am ab073359: (-s ours) Merge "Import translations. DO NOT MERGE" into lmp-preview-dev

* commit 'ab07335962f2b4d34e66bc49d1868b776ecd15ac':
  Import translations. DO NOT MERGE
diff --git a/Android.mk b/Android.mk
index 52459ae..44ffc04 100644
--- a/Android.mk
+++ b/Android.mk
@@ -94,10 +94,10 @@
 	core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
-	core/java/android/app/maintenance/IIdleCallback.aidl \
-	core/java/android/app/maintenance/IIdleService.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
+	core/java/android/bluetooth/IBluetoothA2dpSink.aidl \
+	core/java/android/bluetooth/IBluetoothAvrcpController.aidl \
 	core/java/android/bluetooth/IBluetoothCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
 	core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl \
@@ -110,6 +110,7 @@
 	core/java/android/bluetooth/IBluetoothPbap.aidl \
 	core/java/android/bluetooth/IBluetoothMap.aidl \
 	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
+	core/java/android/bluetooth/IBluetoothHeadsetClient.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
 	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
@@ -118,6 +119,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 \
@@ -148,7 +150,9 @@
 	core/java/android/hardware/display/IDisplayManagerCallback.aidl \
 	core/java/android/hardware/hdmi/IHdmiControlCallback.aidl \
 	core/java/android/hardware/hdmi/IHdmiControlService.aidl \
+	core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl \
+	core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
 	core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
 	core/java/android/hardware/location/IFusedLocationHardware.aidl \
@@ -172,6 +176,7 @@
 	core/java/android/nfc/INfcAdapterExtras.aidl \
 	core/java/android/nfc/INfcTag.aidl \
 	core/java/android/nfc/INfcCardEmulation.aidl \
+	core/java/android/nfc/INfcLockscreenDispatch.aidl \
 	core/java/android/os/IBatteryPropertiesListener.aidl \
 	core/java/android/os/IBatteryPropertiesRegistrar.aidl \
 	core/java/android/os/ICancellationSignal.aidl \
@@ -301,6 +306,7 @@
 	media/java/android/media/IRemoteControlDisplay.aidl \
 	media/java/android/media/IRemoteDisplayCallback.aidl \
 	media/java/android/media/IRemoteDisplayProvider.aidl \
+	media/java/android/media/IRemoteVolumeController.aidl \
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
 	media/java/android/media/IVolumeController.aidl \
@@ -325,11 +331,19 @@
 	telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl \
 	telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl \
 	telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl \
-	telecomm/java/com/android/internal/telecomm/ICallServiceSelector.aidl \
-	telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl \
+	telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl \
+	telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl \
 	telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \
 	telecomm/java/com/android/internal/telecomm/IInCallService.aidl \
 	telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \
+	telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl \
+	telephony/java/com/android/ims/internal/IImsCallSession.aidl \
+	telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl \
+	telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl \
+	telephony/java/com/android/ims/internal/IImsService.aidl \
+	telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
+	telephony/java/com/android/ims/internal/IImsUt.aidl \
+	telephony/java/com/android/ims/internal/IImsUtListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
@@ -340,6 +354,8 @@
 	telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
 	telephony/java/com/android/internal/telephony/ISms.aidl \
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
+	telephony/java/com/android/internal/telephony/ISub.aidl \
+	telephony/java/com/android/internal/telephony/IMms.aidl \
 	wifi/java/android/net/wifi/IWifiManager.aidl \
 	wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
@@ -357,7 +373,7 @@
 			$(framework_res_source_path)/com/android/internal/R.java
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt core core-junit ext okhttp
+LOCAL_JAVA_LIBRARIES := core-libart conscrypt okhttp core-junit bouncycastle ext
 
 LOCAL_MODULE := framework-base
 
@@ -381,54 +397,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 +422,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)
@@ -630,9 +601,9 @@
 	$(framework_res_source_path)/com/android/internal/R.java
 
 framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
-	bouncycastle \
+	core-libart \
 	conscrypt \
-	core \
+	bouncycastle \
 	okhttp \
 	ext \
 	framework \
@@ -649,8 +620,12 @@
 framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html
 # The since flag (-since N.xml API_LEVEL) is used to add API Level information
 # to the reference documentation. Must be in order of oldest to newest.
+#
+# Conscrypt (com.android.org.conscrypt) is an implementation detail and should
+# not be referenced in the documentation.
 framework_docs_LOCAL_DROIDDOC_OPTIONS := \
     -knowntags ./frameworks/base/docs/knowntags.txt \
+    -hidePackage com.android.org.conscrypt \
     -since $(SRC_API_DIR)/1.xml 1 \
     -since $(SRC_API_DIR)/2.xml 2 \
     -since $(SRC_API_DIR)/3.xml 3 \
@@ -670,7 +645,7 @@
     -since $(SRC_API_DIR)/17.txt 17 \
     -since $(SRC_API_DIR)/18.txt 18 \
     -since $(SRC_API_DIR)/19.txt 19 \
-		-werror -hide 113 \
+		-werror -hide 111 -hide 113 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
 framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \
@@ -744,7 +719,7 @@
 $(INTERNAL_PLATFORM_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE))
 
-# ====  the private api stubs ===================================
+# ====  the system api stubs ===================================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
@@ -756,11 +731,11 @@
 LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR)
 LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
 
-LOCAL_MODULE := private-api-stubs
+LOCAL_MODULE := system-api-stubs
 
 LOCAL_DROIDDOC_OPTIONS:=\
 		$(framework_docs_LOCAL_DROIDDOC_OPTIONS) \
-		-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_private_stubs_current_intermediates/src \
+		-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_system_stubs_current_intermediates/src \
         -showAnnotation android.annotation.SystemApi \
 		-nodocs
 
@@ -939,7 +914,7 @@
 LOCAL_SRC_FILES := $(ext_src_files)
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core
+LOCAL_JAVA_LIBRARIES := core-libart
 LOCAL_JAVA_RESOURCE_DIRS := $(ext_res_dirs)
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 5b027b3..73f370d 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -34,9 +34,9 @@
 # made today requires touching the same file, just copy the old
 # touch step and add it to the end of the list.
 #
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
 
 # For example:
 #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
@@ -193,10 +193,19 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/app)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/app/wearable)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/tv/ITv*)
-
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/classes/android/app/task)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/app/task)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/classes/android/app/TaskManager)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/app/maintenance)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/classes/android/app/maintenance)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes/android/app/maintenance)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes/android/app/maintenance)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/app/maintenance)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/api/current.txt b/api/current.txt
index 8d33755..009655c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,12 +21,14 @@
     field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
+    field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     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";
@@ -90,6 +92,7 @@
     field public static final java.lang.String NFC = "android.permission.NFC";
     field public static final deprecated java.lang.String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
     field public static final java.lang.String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    field public static final java.lang.String READ_ALL_VOICEMAIL = "com.android.voicemail.permission.READ_ALL_VOICEMAIL";
     field public static final java.lang.String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final java.lang.String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final java.lang.String READ_CONTACTS = "android.permission.READ_CONTACTS";
@@ -111,6 +114,8 @@
     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 REMOVE_VOICEMAIL = "com.android.voicemail.permission.REMOVE_VOICEMAIL";
     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 +134,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";
@@ -257,10 +263,13 @@
     field public static final int actionModeCloseDrawable = 16843484; // 0x10102dc
     field public static final int actionModeCopyDrawable = 16843538; // 0x1010312
     field public static final int actionModeCutDrawable = 16843537; // 0x1010311
+    field public static final int actionModeFindDrawable = 16843900; // 0x101047c
     field public static final int actionModePasteDrawable = 16843539; // 0x1010313
     field public static final int actionModeSelectAllDrawable = 16843646; // 0x101037e
+    field public static final int actionModeShareDrawable = 16843899; // 0x101047b
     field public static final int actionModeSplitBackground = 16843677; // 0x101039d
     field public static final int actionModeStyle = 16843668; // 0x1010394
+    field public static final int actionModeWebSearchDrawable = 16843901; // 0x101047d
     field public static final int actionOverflowButtonStyle = 16843510; // 0x10102f6
     field public static final int actionOverflowMenuStyle = 16843846; // 0x1010446
     field public static final int actionProviderClass = 16843657; // 0x1010389
@@ -317,6 +326,8 @@
     field public static final int backgroundDimEnabled = 16843295; // 0x101021f
     field public static final int backgroundSplit = 16843659; // 0x101038b
     field public static final int backgroundStacked = 16843658; // 0x101038a
+    field public static final int backgroundTint = 16843885; // 0x101046d
+    field public static final int backgroundTintMode = 16843886; // 0x101046e
     field public static final int backupAgent = 16843391; // 0x101027f
     field public static final int banner = 16843762; // 0x10103f2
     field public static final int baseline = 16843548; // 0x101031c
@@ -341,6 +352,8 @@
     field public static final int buttonStyleInset = 16842826; // 0x101004a
     field public static final int buttonStyleSmall = 16842825; // 0x1010049
     field public static final int buttonStyleToggle = 16842827; // 0x101004b
+    field public static final int buttonTint = 16843889; // 0x1010471
+    field public static final int buttonTintMode = 16843890; // 0x1010472
     field public static final int cacheColorHint = 16843009; // 0x1010101
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
@@ -488,7 +501,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
@@ -502,7 +515,7 @@
     field public static final int excludeClass = 16843844; // 0x1010444
     field public static final int excludeFromRecents = 16842775; // 0x1010017
     field public static final int excludeId = 16843843; // 0x1010443
-    field public static final int excludeViewName = 16843856; // 0x1010450
+    field public static final int excludeName = 16843856; // 0x1010450
     field public static final int exitFadeDuration = 16843533; // 0x101030d
     field public static final int expandableListPreferredChildIndicatorLeft = 16842834; // 0x1010052
     field public static final int expandableListPreferredChildIndicatorRight = 16842835; // 0x1010053
@@ -551,6 +564,8 @@
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
+    field public static final int foregroundTint = 16843887; // 0x101046f
+    field public static final int foregroundTintMode = 16843888; // 0x1010470
     field public static final int format = 16843013; // 0x1010105
     field public static final int format12Hour = 16843722; // 0x10103ca
     field public static final int format24Hour = 16843723; // 0x10103cb
@@ -570,6 +585,7 @@
     field public static final int fromXScale = 16843202; // 0x10101c2
     field public static final int fromYDelta = 16843208; // 0x10101c8
     field public static final int fromYScale = 16843204; // 0x10101c4
+    field public static final int fullBackupOnly = 16843893; // 0x1010475
     field public static final int fullBright = 16842954; // 0x10100ca
     field public static final int fullDark = 16842950; // 0x10100c6
     field public static final int functionalTest = 16842787; // 0x1010023
@@ -633,6 +649,8 @@
     field public static final int indeterminateDuration = 16843069; // 0x101013d
     field public static final int indeterminateOnly = 16843066; // 0x101013a
     field public static final int indeterminateProgressStyle = 16843544; // 0x1010318
+    field public static final int indeterminateTint = 16843883; // 0x101046b
+    field public static final int indeterminateTintMode = 16843884; // 0x101046c
     field public static final int indicatorEnd = 16843730; // 0x10103d2
     field public static final int indicatorLeft = 16843021; // 0x101010d
     field public static final int indicatorRight = 16843022; // 0x101010e
@@ -743,6 +761,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 +773,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 +874,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
@@ -879,7 +900,7 @@
     field public static final int permissionFlags = 16843719; // 0x10103c7
     field public static final int permissionGroup = 16842762; // 0x101000a
     field public static final int permissionGroupFlags = 16843717; // 0x10103c5
-    field public static final int persistable = 16843823; // 0x101042f
+    field public static final int persistableMode = 16843823; // 0x101042f
     field public static final int persistent = 16842765; // 0x101000d
     field public static final int persistentDrawingCache = 16842990; // 0x10100ee
     field public static final deprecated int phoneNumber = 16843111; // 0x1010167
@@ -905,6 +926,8 @@
     field public static final int privateImeOptions = 16843299; // 0x1010223
     field public static final int process = 16842769; // 0x1010011
     field public static final int progress = 16843063; // 0x1010137
+    field public static final int progressBackgroundTint = 16843879; // 0x1010467
+    field public static final int progressBackgroundTintMode = 16843880; // 0x1010468
     field public static final int progressBarPadding = 16843545; // 0x1010319
     field public static final int progressBarStyle = 16842871; // 0x1010077
     field public static final int progressBarStyleHorizontal = 16842872; // 0x1010078
@@ -915,8 +938,12 @@
     field public static final int progressBarStyleSmallInverse = 16843400; // 0x1010288
     field public static final int progressBarStyleSmallTitle = 16843279; // 0x101020f
     field public static final int progressDrawable = 16843068; // 0x101013c
+    field public static final int progressTint = 16843877; // 0x1010465
+    field public static final int progressTintMode = 16843878; // 0x1010466
     field public static final int prompt = 16843131; // 0x101017b
     field public static final int propertyName = 16843489; // 0x10102e1
+    field public static final int propertyXName = 16843894; // 0x1010476
+    field public static final int propertyYName = 16843895; // 0x1010477
     field public static final int protectionLevel = 16842761; // 0x1010009
     field public static final int publicKey = 16843686; // 0x10103a6
     field public static final int queryActionMsg = 16843227; // 0x10101db
@@ -935,6 +962,7 @@
     field public static final int ratingBarStyleIndicator = 16843280; // 0x1010210
     field public static final int ratingBarStyleSmall = 16842877; // 0x101007d
     field public static final int readPermission = 16842759; // 0x1010007
+    field public static final int relinquishTaskIdentity = 16843896; // 0x1010478
     field public static final int repeatCount = 16843199; // 0x10101bf
     field public static final int repeatMode = 16843200; // 0x10101c0
     field public static final int reqFiveWayNav = 16843314; // 0x1010232
@@ -995,6 +1023,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
@@ -1004,12 +1035,14 @@
     field public static final int searchSuggestSelection = 16843224; // 0x10101d8
     field public static final int searchSuggestThreshold = 16843373; // 0x101026d
     field public static final int secondaryProgress = 16843064; // 0x1010138
+    field public static final int secondaryProgressTint = 16843881; // 0x1010469
+    field public static final int secondaryProgressTintMode = 16843882; // 0x101046a
     field public static final int seekBarStyle = 16842875; // 0x101007b
     field public static final int segmentedButtonStyle = 16843568; // 0x1010330
     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
@@ -1126,9 +1159,9 @@
     field public static final int targetClass = 16842799; // 0x101002f
     field public static final int targetDescriptions = 16843680; // 0x10103a0
     field public static final int targetId = 16843740; // 0x10103dc
+    field public static final int targetName = 16843855; // 0x101044f
     field public static final int targetPackage = 16842785; // 0x1010021
     field public static final int targetSdkVersion = 16843376; // 0x1010270
-    field public static final int targetViewName = 16843855; // 0x101044f
     field public static final int taskAffinity = 16842770; // 0x1010012
     field public static final int taskCloseEnterAnimation = 16842942; // 0x10100be
     field public static final int taskCloseExitAnimation = 16842943; // 0x10100bf
@@ -1207,8 +1240,12 @@
     field public static final int thumb = 16843074; // 0x1010142
     field public static final int thumbOffset = 16843075; // 0x1010143
     field public static final int thumbTextPadding = 16843634; // 0x1010372
+    field public static final int thumbTint = 16843891; // 0x1010473
+    field public static final int thumbTintMode = 16843892; // 0x1010474
     field public static final int thumbnail = 16843429; // 0x10102a5
     field public static final int tileMode = 16843265; // 0x1010201
+    field public static final int tileModeX = 16843897; // 0x1010479
+    field public static final int tileModeY = 16843898; // 0x101047a
     field public static final int timeZone = 16843724; // 0x10103cc
     field public static final int tint = 16843041; // 0x1010121
     field public static final int tintMode = 16843797; // 0x1010415
@@ -1236,7 +1273,10 @@
     field public static final int transformPivotY = 16843553; // 0x1010321
     field public static final int transition = 16843743; // 0x10103df
     field public static final int transitionGroup = 16843803; // 0x101041b
+    field public static final int transitionName = 16843802; // 0x101041a
     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
@@ -1268,10 +1308,10 @@
     field public static final int verticalGap = 16843328; // 0x1010240
     field public static final int verticalScrollbarPosition = 16843572; // 0x1010334
     field public static final int verticalSpacing = 16843029; // 0x1010115
-    field public static final int viewName = 16843802; // 0x101041a
     field public static final int viewportHeight = 16843805; // 0x101041d
     field public static final int viewportWidth = 16843804; // 0x101041c
     field public static final int visibility = 16842972; // 0x10100dc
+    field public static final int visibilityMode = 16843902; // 0x101047e
     field public static final int visible = 16843156; // 0x1010194
     field public static final int vmSafeMode = 16843448; // 0x10102b8
     field public static final int voiceLanguage = 16843349; // 0x1010255
@@ -1333,6 +1373,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
@@ -2492,7 +2533,14 @@
 
   public static final class R.transition {
     ctor public R.transition();
+    field public static final int explode = 17760259; // 0x10f0003
+    field public static final int fade = 17760258; // 0x10f0002
+    field public static final int move = 17760257; // 0x10f0001
     field public static final int no_transition = 17760256; // 0x10f0000
+    field public static final int slide_bottom = 17760260; // 0x10f0004
+    field public static final int slide_left = 17760263; // 0x10f0007
+    field public static final int slide_right = 17760262; // 0x10f0006
+    field public static final int slide_top = 17760261; // 0x10f0005
   }
 
   public static final class R.xml {
@@ -4191,6 +4239,9 @@
     method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
     method public abstract android.app.FragmentTransaction setCustomAnimations(int, int);
     method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int);
+    method public abstract android.app.FragmentTransaction setCustomTransition(int, int);
+    method public abstract android.app.FragmentTransaction setSharedElement(android.view.View, java.lang.String);
+    method public abstract android.app.FragmentTransaction setSharedElements(android.util.Pair<android.view.View, java.lang.String>...);
     method public abstract android.app.FragmentTransaction setTransition(int);
     method public abstract android.app.FragmentTransaction setTransitionStyle(int);
     method public abstract android.app.FragmentTransaction show(android.app.Fragment);
@@ -5162,6 +5213,7 @@
     method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent);
     method public void onDisabled(android.content.Context, android.content.Intent);
     method public void onEnabled(android.content.Context, android.content.Intent);
+    method public void onLockTaskModeChanged(android.content.Context, android.content.Intent, boolean, java.lang.String);
     method public void onPasswordChanged(android.content.Context, android.content.Intent);
     method public void onPasswordExpiring(android.content.Context, android.content.Intent);
     method public void onPasswordFailed(android.content.Context, android.content.Intent);
@@ -5171,6 +5223,7 @@
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
+    field public static final java.lang.String ACTION_LOCK_TASK_CHANGED = "android.app.action.ACTION_LOCK_TASK_CHANGED";
     field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED";
     field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING";
     field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
@@ -5178,18 +5231,20 @@
     field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE";
     field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
     field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
+    field public static final java.lang.String EXTRA_LOCK_TASK_ENTERING = "android.app.extra.LOCK_TASK_ENTERING";
+    field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
   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(java.lang.String);
     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);
@@ -5212,12 +5267,16 @@
     method public int getPasswordQuality(android.content.ComponentName);
     method public boolean getStorageEncryption(android.content.ComponentName);
     method public int getStorageEncryptionStatus();
+    method public boolean hasAnyCaCertsInstalled();
+    method public boolean hasCaCertInstalled(byte[]);
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
+    method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     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 isLockTaskPermitted(java.lang.String);
+    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);
@@ -5230,7 +5289,8 @@
     method public void setCameraDisabled(android.content.ComponentName, boolean);
     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 setLockTaskPackages(java.lang.String[]) 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 +5304,15 @@
     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 uninstallCaCert(android.content.ComponentName, byte[]);
     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 +5323,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 +7063,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,9 +7084,11 @@
     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";
+    field public static final java.lang.String TELECOMM_SERVICE = "telecomm";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
     field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
     field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
@@ -7769,12 +7838,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 +7857,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 +7866,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[]);
@@ -7991,6 +8089,7 @@
     field public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1; // 0x1
     field public static final int DOCUMENT_LAUNCH_NEVER = 3; // 0x3
     field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0
+    field public static final int DO_NOT_PERSIST = 1; // 0x1
     field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40
     field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
     field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
@@ -8002,13 +8101,15 @@
     field public static final int FLAG_IMMERSIVE = 2048; // 0x800
     field public static final int FLAG_MULTIPROCESS = 1; // 0x1
     field public static final int FLAG_NO_HISTORY = 128; // 0x80
-    field public static final int FLAG_PERSISTABLE = 4096; // 0x1000
+    field public static final int FLAG_RELINQUISH_TASK_IDENTITY = 4096; // 0x1000
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10
     field public static final int LAUNCH_MULTIPLE = 0; // 0x0
     field public static final int LAUNCH_SINGLE_INSTANCE = 3; // 0x3
     field public static final int LAUNCH_SINGLE_TASK = 2; // 0x2
     field public static final int LAUNCH_SINGLE_TOP = 1; // 0x1
+    field public static final int PERSIST_ACROSS_REBOOTS = 2; // 0x2
+    field public static final int PERSIST_ROOT_ONLY = 0; // 0x0
     field public static final int SCREEN_ORIENTATION_BEHIND = 3; // 0x3
     field public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10; // 0xa
     field public static final int SCREEN_ORIENTATION_FULL_USER = 13; // 0xd
@@ -8033,6 +8134,7 @@
     field public int maxRecents;
     field public java.lang.String parentActivityName;
     field public java.lang.String permission;
+    field public int persistableMode;
     field public int screenOrientation;
     field public int softInputMode;
     field public java.lang.String targetActivity;
@@ -8054,6 +8156,7 @@
     field public static final int FLAG_DEBUGGABLE = 2; // 0x2
     field public static final int FLAG_EXTERNAL_STORAGE = 262144; // 0x40000
     field public static final int FLAG_FACTORY_TEST = 16; // 0x10
+    field public static final int FLAG_FULL_BACKUP_ONLY = 67108864; // 0x4000000
     field public static final int FLAG_HAS_CODE = 4; // 0x4
     field public static final int FLAG_INSTALLED = 8388608; // 0x800000
     field public static final int FLAG_IS_DATA_ONLY = 16777216; // 0x1000000
@@ -8090,6 +8193,8 @@
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitPublicSourceDirs;
+    field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
     field public java.lang.String taskAffinity;
     field public int theme;
@@ -8158,6 +8263,8 @@
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitPublicSourceDirs;
+    field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
   }
 
@@ -8187,15 +8294,26 @@
   }
 
   public class LauncherApps {
+    method public void addOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback);
     method public void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener);
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
     method public boolean isActivityEnabledForProfile(android.content.ComponentName, android.os.UserHandle);
     method public boolean isPackageEnabledForProfile(java.lang.String, android.os.UserHandle);
+    method public void removeOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback);
     method public void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
     method public void startActivityForProfile(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
   }
 
+  public static abstract class LauncherApps.OnAppsChangedCallback {
+    ctor public LauncherApps.OnAppsChangedCallback();
+    method public abstract void onPackageAdded(java.lang.String, android.os.UserHandle);
+    method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle);
+    method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle);
+    method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean);
+    method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
+  }
+
   public static abstract interface LauncherApps.OnAppsChangedListener {
     method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String);
     method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String);
@@ -10075,13 +10193,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,11 +10270,12 @@
     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);
+    method public void drawArc(float, float, float, float, float, float, boolean, android.graphics.Paint);
     method public void drawBitmap(android.graphics.Bitmap, float, float, android.graphics.Paint);
     method public void drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.RectF, android.graphics.Paint);
     method public void drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.Rect, android.graphics.Paint);
@@ -10171,6 +10290,7 @@
     method public void drawLines(float[], int, int, android.graphics.Paint);
     method public void drawLines(float[], android.graphics.Paint);
     method public void drawOval(android.graphics.RectF, android.graphics.Paint);
+    method public void drawOval(float, float, float, float, android.graphics.Paint);
     method public void drawPaint(android.graphics.Paint);
     method public void drawPath(android.graphics.Path, android.graphics.Paint);
     method public void drawPicture(android.graphics.Picture);
@@ -10680,6 +10800,7 @@
     ctor public Path();
     ctor public Path(android.graphics.Path);
     method public void addArc(android.graphics.RectF, float, float);
+    method public void addArc(float, float, float, float, float, float);
     method public void addCircle(float, float, float, android.graphics.Path.Direction);
     method public void addOval(android.graphics.RectF, android.graphics.Path.Direction);
     method public void addOval(float, float, float, float, android.graphics.Path.Direction);
@@ -10689,9 +10810,12 @@
     method public void addRect(android.graphics.RectF, android.graphics.Path.Direction);
     method public void addRect(float, float, float, float, android.graphics.Path.Direction);
     method public void addRoundRect(android.graphics.RectF, float, float, android.graphics.Path.Direction);
+    method public void addRoundRect(float, float, float, float, float, float, android.graphics.Path.Direction);
     method public void addRoundRect(android.graphics.RectF, float[], android.graphics.Path.Direction);
+    method public void addRoundRect(float, float, float, float, float[], android.graphics.Path.Direction);
     method public void arcTo(android.graphics.RectF, float, float, boolean);
     method public void arcTo(android.graphics.RectF, float, float);
+    method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
     method public void computeBounds(android.graphics.RectF, boolean);
     method public void cubicTo(float, float, float, float, float, float);
@@ -11137,6 +11261,17 @@
     method public void addTransition(int, int, android.graphics.drawable.AnimationDrawable, boolean);
   }
 
+  public class AnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
+    ctor public AnimatedVectorDrawable();
+    method public void draw(android.graphics.Canvas);
+    method public int getOpacity();
+    method public boolean isRunning();
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void start();
+    method public void stop();
+  }
+
   public class AnimationDrawable extends android.graphics.drawable.DrawableContainer implements android.graphics.drawable.Animatable java.lang.Runnable {
     ctor public AnimationDrawable();
     method public void addFrame(android.graphics.drawable.Drawable, int);
@@ -11450,7 +11585,7 @@
   }
 
   public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
-    ctor public RippleDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+    ctor public RippleDrawable(android.content.res.ColorStateList, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public void setColor(android.content.res.ColorStateList);
   }
 
@@ -11530,6 +11665,14 @@
     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);
+  }
+
 }
 
 package android.graphics.drawable.shapes {
@@ -11900,15 +12043,23 @@
   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();
     method public float getPower();
+    method public int getReportingMode();
     method public float getResolution();
     method public java.lang.String getStringType();
     method public int getType();
     method public java.lang.String getVendor();
     method public int getVersion();
+    method public boolean isWakeUpSensor();
+    field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
+    field public static final int REPORTING_MODE_ONE_SHOT = 2; // 0x2
+    field public static final int REPORTING_MODE_ON_CHANGE = 1; // 0x1
+    field public static final int REPORTING_MODE_SPECIAL_TRIGGER = 3; // 0x3
+    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 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 +12081,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 +12112,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 +12122,24 @@
     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
   }
 
   public class SensorEvent {
@@ -12053,6 +12241,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
@@ -12171,6 +12360,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_INFO_PHYSICAL_SIZE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_INFO_PIXEL_ARRAY_SIZE;
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_INFO_SENSITIVITY_RANGE;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_INFO_TIMESTAMP_CALIBRATION;
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_INFO_WHITE_LEVEL;
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_MAX_ANALOG_SENSITIVITY;
     field public static final android.hardware.camera2.CameraCharacteristics.Key SENSOR_ORIENTATION;
@@ -12329,6 +12519,7 @@
     field public static final int CONTROL_SCENE_MODE_DISABLED = 0; // 0x0
     field public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; // 0x1
     field public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; // 0xc
+    field public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11
     field public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; // 0x4
     field public static final int CONTROL_SCENE_MODE_NIGHT = 5; // 0x5
     field public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; // 0x6
@@ -12379,6 +12570,8 @@
     field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG = 1; // 0x1
     field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4; // 0x4
     field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB = 0; // 0x0
+    field public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_CALIBRATED = 1; // 0x1
+    field public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_UNCALIBRATED = 0; // 0x0
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; // 0xa
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; // 0xe
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; // 0x17
@@ -12557,6 +12750,7 @@
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_FRAME_DURATION;
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_GREEN_SPLIT;
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_NEUTRAL_COLOR_POINT;
+    field public static final android.hardware.camera2.CaptureResult.Key SENSOR_ROLLING_SHUTTER_SKEW;
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_SENSITIVITY;
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_TEST_PATTERN_DATA;
     field public static final android.hardware.camera2.CaptureResult.Key SENSOR_TEST_PATTERN_MODE;
@@ -12667,6 +12861,7 @@
     method public boolean isOutputSupportedFor(int);
     method public static boolean isOutputSupportedFor(java.lang.Class<T>);
     method public boolean isOutputSupportedFor(android.view.Surface);
+    field public static final long NO_MIN_FRAME_DURATION = 0L; // 0x0L
   }
 
   public final class TonemapCurve {
@@ -13496,8 +13691,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
@@ -13536,7 +13769,9 @@
     field public static final int CHANNEL_OUT_QUAD = 204; // 0xcc
     field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
     field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+    field public static final int ENCODING_AC3 = 5; // 0x5
     field public static final int ENCODING_DEFAULT = 1; // 0x1
+    field public static final int ENCODING_E_AC3 = 6; // 0x6
     field public static final int ENCODING_INVALID = 0; // 0x0
     field public static final int ENCODING_PCM_16BIT = 2; // 0x2
     field public static final int ENCODING_PCM_8BIT = 3; // 0x3
@@ -13548,6 +13783,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);
@@ -13587,6 +13823,7 @@
     method public deprecated void setWiredHeadsetOn(boolean);
     method public deprecated boolean shouldVibrate(int);
     method public void startBluetoothSco();
+    method public void startBluetoothScoVirtualCall();
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
@@ -13608,6 +13845,8 @@
     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 int ERROR_DEAD_OBJECT = -6; // 0xfffffffa
     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 +14026,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 +14035,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
@@ -14230,7 +14471,7 @@
     method public java.util.List<byte[]> getSecureStops();
     method public static final boolean isCryptoSchemeSupported(java.util.UUID);
     method public static final boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String);
-    method public byte[] openSession() throws android.media.NotProvisionedException;
+    method public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
     method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException;
     method public java.util.HashMap<java.lang.String, java.lang.String> queryKeyStatus(byte[]);
@@ -14267,6 +14508,11 @@
     method public java.lang.String getDefaultUrl();
   }
 
+  public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
+    ctor public MediaDrm.MediaDrmStateException(int, java.lang.String);
+    method public int getErrorCode();
+  }
+
   public static abstract interface MediaDrm.OnEventListener {
     method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
   }
@@ -15135,6 +15381,19 @@
     ctor public UnsupportedSchemeException(java.lang.String);
   }
 
+  public abstract class VolumeProvider {
+    ctor public VolumeProvider(int, int);
+    method public final int getMaxVolume();
+    method public final int getVolumeControl();
+    method public final void notifyVolumeChanged();
+    method public void onAdjustVolumeBy(int);
+    method public abstract int onGetCurrentVolume();
+    method public void onSetVolumeTo(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
 }
 
 package android.media.audiofx {
@@ -15495,14 +15754,17 @@
   public final class MediaController {
     method public void addCallback(android.media.session.MediaController.Callback);
     method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
+    method public void adjustVolumeBy(int, int);
     method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
     method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
     method public android.media.MediaMetadata getMetadata();
     method public android.media.session.PlaybackState getPlaybackState();
     method public int getRatingType();
     method public android.media.session.MediaController.TransportControls getTransportControls();
+    method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
     method public void removeCallback(android.media.session.MediaController.Callback);
     method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public void setVolumeTo(int, int);
   }
 
   public static abstract class MediaController.Callback {
@@ -15510,6 +15772,7 @@
     method public void onMetadataChanged(android.media.MediaMetadata);
     method public void onPlaybackStateChanged(android.media.session.PlaybackState);
     method public void onSessionEvent(java.lang.String, android.os.Bundle);
+    method public void onVolumeInfoChanged(android.media.session.MediaController.VolumeInfo);
   }
 
   public final class MediaController.TransportControls {
@@ -15524,6 +15787,14 @@
     method public void stop();
   }
 
+  public static final class MediaController.VolumeInfo {
+    method public int getAudioStream();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getVolumeControl();
+    method public int getVolumeType();
+  }
+
   public final class MediaSession {
     method public void addCallback(android.media.session.MediaSession.Callback);
     method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
@@ -15541,9 +15812,11 @@
     method public void setMetadata(android.media.MediaMetadata);
     method public void setPlaybackState(android.media.session.PlaybackState);
     method public void setPlaybackToLocal(int);
-    method public void setPlaybackToRemote(android.media.session.RemoteVolumeProvider);
+    method public void setPlaybackToRemote(android.media.VolumeProvider);
     field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
   }
 
   public static abstract class MediaSession.Callback {
@@ -15566,7 +15839,14 @@
   }
 
   public final class MediaSessionManager {
+    method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
     method public android.media.session.MediaSession createSession(java.lang.String);
+    method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
+  }
+
+  public static abstract class MediaSessionManager.SessionListener {
+    ctor public MediaSessionManager.SessionListener();
+    method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>);
   }
 
   public final class MediaSessionToken implements android.os.Parcelable {
@@ -15614,19 +15894,6 @@
     field public static final int STATE_STOPPED = 1; // 0x1
   }
 
-  public abstract class RemoteVolumeProvider {
-    ctor public RemoteVolumeProvider(int, int);
-    method public final int getMaxVolume();
-    method public final int getVolumeControl();
-    method public final void notifyVolumeChanged();
-    method public void onAdjustVolumeBy(int);
-    method public abstract int onGetCurrentVolume();
-    method public void onSetVolumeTo(int);
-    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
-    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
-    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
-  }
-
 }
 
 package android.media.tv {
@@ -15638,7 +15905,9 @@
     method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName);
     method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName, boolean);
     method public static final android.net.Uri buildProgramUri(long);
+    method public static final android.net.Uri buildProgramsUriForChannel(long);
     method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri);
+    method public static final android.net.Uri buildProgramsUriForChannel(long, long, long);
     method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long);
     field public static final java.lang.String AUTHORITY = "android.media.tv";
   }
@@ -15648,6 +15917,7 @@
   }
 
   public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
+    method public static final java.lang.String getVideoResolution(java.lang.String);
     field public static final java.lang.String COLUMN_BROWSABLE = "browsable";
     field public static final java.lang.String COLUMN_DESCRIPTION = "description";
     field public static final java.lang.String COLUMN_DISPLAY_NAME = "display_name";
@@ -15661,6 +15931,7 @@
     field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
+    field public static final java.lang.String COLUMN_VIDEO_FORMAT = "video_format";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/channel";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/channel";
     field public static final android.net.Uri CONTENT_URI;
@@ -15685,10 +15956,29 @@
     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
+    field public static final java.lang.String VIDEO_FORMAT_1080I = "VIDEO_FORMAT_1080I";
+    field public static final java.lang.String VIDEO_FORMAT_1080P = "VIDEO_FORMAT_1080P";
+    field public static final java.lang.String VIDEO_FORMAT_2160P = "VIDEO_FORMAT_2160P";
+    field public static final java.lang.String VIDEO_FORMAT_240P = "VIDEO_FORMAT_240P";
+    field public static final java.lang.String VIDEO_FORMAT_360P = "VIDEO_FORMAT_360P";
+    field public static final java.lang.String VIDEO_FORMAT_4320P = "VIDEO_FORMAT_4320P";
+    field public static final java.lang.String VIDEO_FORMAT_480I = "VIDEO_FORMAT_480I";
+    field public static final java.lang.String VIDEO_FORMAT_480P = "VIDEO_FORMAT_480P";
+    field public static final java.lang.String VIDEO_FORMAT_576I = "VIDEO_FORMAT_576I";
+    field public static final java.lang.String VIDEO_FORMAT_576P = "VIDEO_FORMAT_576P";
+    field public static final java.lang.String VIDEO_FORMAT_720P = "VIDEO_FORMAT_720P";
+    field public static final java.lang.String VIDEO_RESOLUTION_ED = "VIDEO_RESOLUTION_ED";
+    field public static final java.lang.String VIDEO_RESOLUTION_FHD = "VIDEO_RESOLUTION_FHD";
+    field public static final java.lang.String VIDEO_RESOLUTION_HD = "VIDEO_RESOLUTION_HD";
+    field public static final java.lang.String VIDEO_RESOLUTION_SD = "VIDEO_RESOLUTION_SD";
+    field public static final java.lang.String VIDEO_RESOLUTION_UHD = "VIDEO_RESOLUTION_UHD";
   }
 
   public static final class TvContract.Channels.Logo {
@@ -15709,6 +15999,8 @@
     field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
+    field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
+    field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
     field public static final android.net.Uri CONTENT_URI;
@@ -15717,17 +16009,17 @@
   public static final class TvContract.Programs.Genres {
     method public static java.lang.String[] decode(java.lang.String);
     method public static java.lang.String encode(java.lang.String...);
-    field public static final java.lang.String ANIMAL_WILDLIFE = "Animal/Wildlife";
-    field public static final java.lang.String COMEDY = "Comedy";
-    field public static final java.lang.String DRAMA = "Drama";
-    field public static final java.lang.String EDUCATION = "Education";
-    field public static final java.lang.String FAMILY_KIDS = "Family/Kids";
-    field public static final java.lang.String GAMING = "Gaming";
-    field public static final java.lang.String MOVIES = "Movies";
-    field public static final java.lang.String NEWS = "News";
-    field public static final java.lang.String SHOPPING = "Shopping";
-    field public static final java.lang.String SPORTS = "Sports";
-    field public static final java.lang.String TRAVEL = "Travel";
+    field public static final java.lang.String ANIMAL_WILDLIFE = "ANIMAL_WILDLIFE";
+    field public static final java.lang.String COMEDY = "COMEDY";
+    field public static final java.lang.String DRAMA = "DRAMA";
+    field public static final java.lang.String EDUCATION = "EDUCATION";
+    field public static final java.lang.String FAMILY_KIDS = "FAMILY_KIDS";
+    field public static final java.lang.String GAMING = "GAMING";
+    field public static final java.lang.String MOVIES = "MOVIES";
+    field public static final java.lang.String NEWS = "NEWS";
+    field public static final java.lang.String SHOPPING = "SHOPPING";
+    field public static final java.lang.String SPORTS = "SPORTS";
+    field public static final java.lang.String TRAVEL = "TRAVEL";
   }
 
   public final class TvInputInfo implements android.os.Parcelable {
@@ -15737,6 +16029,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";
@@ -16213,6 +16506,18 @@
     method public android.net.NetworkRequest.Builder removeTransportType(int);
   }
 
+  public abstract interface PSKKeyManager {
+    method public abstract java.lang.String chooseClientKeyIdentity(java.lang.String, java.net.Socket);
+    method public abstract java.lang.String chooseClientKeyIdentity(java.lang.String, javax.net.ssl.SSLEngine);
+    method public abstract java.lang.String chooseServerKeyIdentityHint(java.net.Socket);
+    method public abstract java.lang.String chooseServerKeyIdentityHint(javax.net.ssl.SSLEngine);
+    method public abstract javax.crypto.SecretKey getKey(java.lang.String, java.lang.String, java.net.Socket);
+    method public abstract javax.crypto.SecretKey getKey(java.lang.String, java.lang.String, javax.net.ssl.SSLEngine);
+    field public static final int MAX_IDENTITY_HINT_LENGTH_BYTES = 128; // 0x80
+    field public static final int MAX_IDENTITY_LENGTH_BYTES = 128; // 0x80
+    field public static final int MAX_KEY_LENGTH_BYTES = 256; // 0x100
+  }
+
   public class ParseException extends java.lang.RuntimeException {
     field public java.lang.String response;
   }
@@ -16982,11 +17287,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
   }
@@ -17020,6 +17323,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);
@@ -17043,9 +17347,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";
@@ -17056,6 +17363,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";
@@ -17071,6 +17380,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 {
@@ -17088,11 +17407,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
@@ -17366,6 +17692,7 @@
     method public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
     method public boolean isNdefPushEnabled();
+    method public boolean registerLockscreenDispatch(android.nfc.NfcAdapter.NfcLockscreenDispatch, java.lang.String[]);
     method public void setBeamPushUris(android.net.Uri[], android.app.Activity);
     method public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
@@ -17401,6 +17728,10 @@
     method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
   }
 
+  public static abstract interface NfcAdapter.NfcLockscreenDispatch {
+    method public abstract boolean onTagDetected(android.nfc.Tag);
+  }
+
   public static abstract interface NfcAdapter.OnNdefPushCompleteCallback {
     method public abstract void onNdefPushComplete(android.nfc.NfcEvent);
   }
@@ -21324,6 +21655,7 @@
   public class UserManager {
     method public android.os.Bundle getApplicationRestrictions(java.lang.String);
     method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle);
+    method public java.lang.String getBadgedLabelForUser(java.lang.String, android.os.UserHandle);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method public int getUserCount();
     method public android.os.UserHandle getUserForSerialNumber(long);
@@ -21331,6 +21663,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);
@@ -21340,7 +21673,7 @@
     method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
-    field public static final java.lang.String DISALLOW_CONFIG_APPS = "no_config_apps";
+    field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -21367,7 +21700,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 {
@@ -22134,6 +22469,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";
@@ -22534,6 +22870,7 @@
     field public static final java.lang.String CACHED_NUMBER_LABEL = "numberlabel";
     field public static final java.lang.String CACHED_NUMBER_TYPE = "numbertype";
     field public static final java.lang.String CACHED_PHOTO_ID = "photo_id";
+    field public static final java.lang.String SUBSCRIPTION_COMPONENT_NAME = "component_name";
     field public static final android.net.Uri CONTENT_FILTER_URI;
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
@@ -22557,6 +22894,7 @@
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
     field public static final int PRESENTATION_UNKNOWN = 3; // 0x3
+    field public static final java.lang.String SUBSCRIPTION_ID = "subscription_id";
     field public static final java.lang.String TYPE = "type";
     field public static final int VOICEMAIL_TYPE = 4; // 0x4
     field public static final java.lang.String VOICEMAIL_URI = "voicemail_uri";
@@ -22862,6 +23200,7 @@
     field public static final java.lang.String AUTHORITY = "com.android.contacts";
     field public static final android.net.Uri AUTHORITY_URI;
     field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+    field public static final java.lang.String DEFERRED_SNIPPETING = "deferred_snippeting";
     field public static final java.lang.String DIRECTORY_PARAM_KEY = "directory";
     field public static final java.lang.String LIMIT_PARAM_KEY = "limit";
     field public static final java.lang.String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
@@ -23121,6 +23460,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";
@@ -23134,6 +23480,7 @@
   protected static abstract interface ContactsContract.ContactOptionsColumns {
     field public static final java.lang.String CUSTOM_RINGTONE = "custom_ringtone";
     field public static final java.lang.String LAST_TIME_CONTACTED = "last_time_contacted";
+    field public static final java.lang.String PINNED = "pinned";
     field public static final java.lang.String SEND_TO_VOICEMAIL = "send_to_voicemail";
     field public static final java.lang.String STARRED = "starred";
     field public static final java.lang.String TIMES_CONTACTED = "times_contacted";
@@ -23157,6 +23504,7 @@
     method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean);
     method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri);
     field public static final android.net.Uri CONTENT_FILTER_URI;
+    field public static final android.net.Uri CONTENT_FREQUENT_URI;
     field public static final android.net.Uri CONTENT_GROUP_URI;
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
     field public static final android.net.Uri CONTENT_LOOKUP_URI;
@@ -23177,7 +23525,7 @@
     field public static final java.lang.String CONTENT_DIRECTORY = "data";
   }
 
-  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
+  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.DataUsageStatColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
     field public static final java.lang.String CONTENT_DIRECTORY = "entities";
     field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
@@ -23202,6 +23550,7 @@
     field public static final java.lang.String IN_VISIBLE_GROUP = "in_visible_group";
     field public static final java.lang.String IS_USER_PROFILE = "is_user_profile";
     field public static final java.lang.String LOOKUP_KEY = "lookup";
+    field public static final java.lang.String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
     field public static final java.lang.String PHOTO_FILE_ID = "photo_file_id";
     field public static final java.lang.String PHOTO_ID = "photo_id";
     field public static final java.lang.String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
@@ -23237,6 +23586,7 @@
     field public static final java.lang.String IS_SUPER_PRIMARY = "is_super_primary";
     field public static final java.lang.String MIMETYPE = "mimetype";
     field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
+    field public static final java.lang.String RES_PACKAGE = "res_package";
     field public static final java.lang.String SYNC1 = "data_sync1";
     field public static final java.lang.String SYNC2 = "data_sync2";
     field public static final java.lang.String SYNC3 = "data_sync3";
@@ -23340,11 +23690,13 @@
     field public static final java.lang.String GROUP_IS_READ_ONLY = "group_is_read_only";
     field public static final java.lang.String GROUP_VISIBLE = "group_visible";
     field public static final java.lang.String NOTES = "notes";
+    field public static final java.lang.String RES_PACKAGE = "res_package";
     field public static final java.lang.String SHOULD_SYNC = "should_sync";
     field public static final java.lang.String SUMMARY_COUNT = "summ_count";
     field public static final java.lang.String SUMMARY_WITH_PHONES = "summ_phones";
     field public static final java.lang.String SYSTEM_ID = "system_id";
     field public static final java.lang.String TITLE = "title";
+    field public static final java.lang.String TITLE_RES = "title_res";
   }
 
   public static final class ContactsContract.Intents {
@@ -23411,6 +23763,13 @@
     field public static final int UNDEFINED = 0; // 0x0
   }
 
+  public static final class ContactsContract.PinnedPositions {
+    ctor public ContactsContract.PinnedPositions();
+    field public static final int DEMOTED = -1; // 0xffffffff
+    field public static final java.lang.String UNDEMOTE_METHOD = "undemote";
+    field public static final int UNPINNED = 2147483647; // 0x7fffffff
+  }
+
   public static final class ContactsContract.Preferences {
     ctor public ContactsContract.Preferences();
     field public static final java.lang.String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER";
@@ -23453,6 +23812,8 @@
     ctor public ContactsContract.QuickContact();
     method public static void showQuickContact(android.content.Context, android.view.View, android.net.Uri, int, java.lang.String[]);
     method public static void showQuickContact(android.content.Context, android.graphics.Rect, android.net.Uri, int, java.lang.String[]);
+    field public static final java.lang.String ACTION_QUICK_CONTACT = "com.android.contacts.action.QUICK_CONTACT";
+    field public static final java.lang.String EXTRA_EXCLUDE_MIMES = "exclude_mimes";
     field public static final int MODE_LARGE = 3; // 0x3
     field public static final int MODE_MEDIUM = 2; // 0x2
     field public static final int MODE_SMALL = 1; // 0x1
@@ -23488,10 +23849,12 @@
   }
 
   protected static abstract interface ContactsContract.RawContactsColumns {
+    field public static final java.lang.String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
     field public static final java.lang.String AGGREGATION_MODE = "aggregation_mode";
     field public static final java.lang.String CONTACT_ID = "contact_id";
     field public static final java.lang.String DATA_SET = "data_set";
     field public static final java.lang.String DELETED = "deleted";
+    field public static final java.lang.String NAME_VERIFIED = "name_verified";
     field public static final java.lang.String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
     field public static final java.lang.String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
   }
@@ -23503,6 +23866,13 @@
     field public static final android.net.Uri PROFILE_CONTENT_URI;
   }
 
+  public static class ContactsContract.SearchSnippetColumns {
+    ctor public ContactsContract.SearchSnippetColumns();
+    field public static final java.lang.String DEFERRED_SNIPPETING_KEY = "deferred_snippeting";
+    field public static final java.lang.String SNIPPET = "snippet";
+    field public static final java.lang.String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
+  }
+
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
@@ -23893,7 +24263,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";
   }
 
@@ -24216,6 +24585,7 @@
     field public static final java.lang.String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
     field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
     field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
+    field public static final java.lang.String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
     field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
     field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
     field public static final deprecated java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
@@ -25421,7 +25791,9 @@
 
   public class ScriptC extends android.renderscript.Script {
     ctor protected ScriptC(int, android.renderscript.RenderScript);
+    ctor protected ScriptC(long, android.renderscript.RenderScript);
     ctor protected ScriptC(android.renderscript.RenderScript, android.content.res.Resources, int);
+    ctor protected ScriptC(android.renderscript.RenderScript, java.lang.String, byte[], byte[]);
   }
 
   public final class ScriptGroup extends android.renderscript.BaseObj {
@@ -25802,6 +26174,51 @@
 
 }
 
+package android.service.fingerprint {
+
+  public class FingerprintManager {
+    method public void enroll(long);
+    method public void enrollCancel();
+    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_ACQUIRED = 1; // 0x1
+    field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+    field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 4; // 0x4
+    field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+    field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+    field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 16; // 0x10
+    field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 8; // 0x8
+    field public static final int FINGERPRINT_ERROR = -1; // 0xffffffff
+    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_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+    field public static final int FINGERPRINT_PROCESSED = 2; // 0x2
+    field public static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; // 0x3
+    field public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; // 0x4
+  }
+
+  public class FingerprintManagerReceiver {
+    ctor public FingerprintManagerReceiver();
+    method public void onAcquired(int);
+    method public void onEnrollResult(int, int);
+    method public void onError(int);
+    method public void onProcessed(int);
+    method public void onRemoved(int);
+  }
+
+  public class FingerprintUtils {
+    ctor public FingerprintUtils();
+    method public static void addFingerprintIdForUser(int, android.content.ContentResolver, int);
+    method public static int[] getFingerprintIdsForUser(android.content.ContentResolver, int);
+    method public static boolean removeFingerprintIdForUser(int, android.content.ContentResolver, int);
+  }
+
+}
+
 package android.service.notification {
 
   public abstract class NotificationListenerService extends android.app.Service {
@@ -25881,6 +26298,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 {
@@ -26090,11 +26521,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);
   }
 
@@ -26110,7 +26604,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);
@@ -26195,8 +26701,83 @@
     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.util.Locale getDefaultLocale();
+    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();
@@ -26204,6 +26785,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 {
@@ -26213,6 +26875,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 {
@@ -26846,6 +27546,446 @@
 
 }
 
+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 final class CallCameraCapabilities implements android.os.Parcelable {
+    ctor public CallCameraCapabilities(boolean, float);
+    method public int describeContents();
+    method public float getMaxZoom();
+    method public boolean isZoomSupported();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public final class CallCapabilities {
+    method public static java.lang.String toString(int);
+    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 android.telecomm.Subscription getSubscription();
+    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 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 implements android.os.IBinder.DeathRecipient {
+    method public void addConferenceCall(java.lang.String);
+    method public void binderDied();
+    method public void cancelOutgoingCall(java.lang.String);
+    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 setCallVideoProvider(java.lang.String, android.telecomm.CallVideoProvider);
+    method public void setDialing(java.lang.String);
+    method public void setDisconnected(java.lang.String, int, java.lang.String);
+    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 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 CallVideoClient {
+    ctor protected CallVideoClient();
+    method public abstract void onCallSessionEvent(int);
+    method public abstract void onCameraCapabilitiesChange(android.telecomm.CallCameraCapabilities);
+    method public abstract void onReceiveSessionModifyRequest(android.telecomm.VideoCallProfile);
+    method public abstract void onReceiveSessionModifyResponse(int, android.telecomm.VideoCallProfile, android.telecomm.VideoCallProfile);
+    method public abstract void onUpdateCallDataUsage(int);
+    method public abstract void onUpdatedPeerDimensions(int, int);
+    field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
+    field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
+    field public static final int SESSION_EVENT_TX_START = 3; // 0x3
+    field public static final int SESSION_EVENT_TX_STOP = 4; // 0x4
+    field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
+    field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+  }
+
+  public abstract class CallVideoProvider {
+    ctor protected CallVideoProvider();
+    method public abstract void setCamera(java.lang.String);
+  }
+
+  public class CallVideoProviderWrapper implements android.os.IBinder.DeathRecipient {
+    method public void binderDied();
+    method public void setCamera(java.lang.String) throws android.os.RemoteException;
+  }
+
+  public abstract class Connection {
+    ctor protected Connection();
+    method public final android.telecomm.CallAudioState getCallAudioState();
+    method public final java.util.List<android.telecomm.Connection> getChildConnections();
+    method public final android.net.Uri getHandle();
+    method public final android.telecomm.Connection getParentConnection();
+    method public final int getState();
+    method public final boolean isConferenceCapable();
+    method public final boolean isConferenceConnection();
+    method public final boolean isRequestingRingback();
+    method protected void onAbort();
+    method protected void onAnswer();
+    method protected void onChildrenChanged(java.util.List<android.telecomm.Connection>);
+    method protected void onConference();
+    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 onSeparate();
+    method protected void onSetAudioState(android.telecomm.CallAudioState);
+    method protected void onSetState(int);
+    method protected void onStopDtmfTone();
+    method protected void onUnhold();
+    method public final void setActive();
+    method public final void setCallVideoProvider(android.telecomm.CallVideoProvider);
+    method public final void setDestroyed();
+    method public final void setDialing();
+    method public final void setDisconnected(int, java.lang.String);
+    method public final void setHandle(android.net.Uri);
+    method public final void setIsConferenceCapable(boolean);
+    method public final void setOnHold();
+    method public final void setParentConnection(android.telecomm.Connection);
+    method public final void setRequestingRingback(boolean);
+    method public final void setRinging();
+    method public final void setSignal(android.os.Bundle);
+    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 onConferenceCapableChanged(android.telecomm.Connection, boolean);
+    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 onParentConnectionChanged(android.telecomm.Connection, android.telecomm.Connection);
+    method public abstract void onRequestingRingback(android.telecomm.Connection, boolean);
+    method public abstract void onSetCallVideoProvider(android.telecomm.Connection, android.telecomm.CallVideoProvider);
+    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 onConferenceCapableChanged(android.telecomm.Connection, boolean);
+    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 onParentConnectionChanged(android.telecomm.Connection, android.telecomm.Connection);
+    method public void onRequestingRingback(android.telecomm.Connection, boolean);
+    method public void onSetCallVideoProvider(android.telecomm.Connection, android.telecomm.CallVideoProvider);
+    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);
+    ctor public ConnectionRequest(android.telecomm.Subscription, 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 android.telecomm.Subscription getSubscription();
+    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 void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+    method public final void disconnect(java.lang.String);
+    method public java.util.Collection<android.telecomm.Connection> getAllConnections();
+    method public final void hold(java.lang.String);
+    method public void lookupRemoteSubscriptions(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.Subscription>>);
+    method public void maybeRespondToSubscriptionLookup();
+    method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState);
+    method public void onConnectionAdded(android.telecomm.Connection);
+    method public void onConnectionRemoved(android.telecomm.Connection);
+    method public void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response<java.lang.String, android.telecomm.Connection>);
+    method public void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.Connection>);
+    method public void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+    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 static abstract interface ConnectionService.OutgoingCallResponse {
+    method public abstract void onCancel(android.telecomm.ConnectionRequest);
+    method public abstract void onFailure(android.telecomm.ConnectionRequest, int, java.lang.String);
+    method public abstract void onSuccess(android.telecomm.ConnectionRequest, CONNECTION);
+  }
+
+  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, boolean, 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 java.util.List<java.lang.String> getCannedSmsResponses();
+    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 android.telecomm.Subscription getSubscription();
+    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 final class RemoteConnection {
+    method public void addListener(android.telecomm.RemoteConnection.Listener);
+    method public void answer();
+    method public void disconnect();
+    method public int getDisconnectCause();
+    method public java.lang.String getDisconnectMessage();
+    method public void hold();
+    method public void playDtmf(char);
+    method public void postDialContinue(boolean);
+    method public void reject();
+    method public void removeListener(android.telecomm.RemoteConnection.Listener);
+    method public void stopDtmf();
+    method public void unhold();
+  }
+
+  public static abstract interface RemoteConnection.Listener {
+    method public abstract void onAudioStateChanged(android.telecomm.RemoteConnection, android.telecomm.CallAudioState);
+    method public abstract void onDestroyed(android.telecomm.RemoteConnection);
+    method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String);
+    method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String);
+    method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
+    method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
+  }
+
+  public class RemoteConnectionService implements android.os.IBinder.DeathRecipient {
+    method public void binderDied();
+    method public void createOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+    method public java.util.List<android.telecomm.Subscription> lookupSubscriptions(android.net.Uri);
+  }
+
+  public abstract interface Response {
+    method public abstract void onError(IN, int, java.lang.String);
+    method public abstract void onResult(IN, OUT...);
+  }
+
+  public abstract interface SimpleResponse {
+    method public abstract void onError(IN);
+    method public abstract void onResult(IN, OUT);
+  }
+
+  public class Subscription implements android.os.Parcelable {
+    ctor public Subscription(android.content.ComponentName, java.lang.String, android.net.Uri, int, int, int, boolean, boolean);
+    method public int describeContents();
+    method public android.content.ComponentName getComponentName();
+    method public android.net.Uri getHandle();
+    method public android.graphics.drawable.Drawable getIcon(android.content.Context);
+    method public android.graphics.drawable.Drawable getIcon(android.content.Context, int);
+    method public java.lang.String getId();
+    method public java.lang.String getLabel(android.content.Context);
+    method public java.lang.String getShortDescription(android.content.Context);
+    method public boolean isEnabled();
+    method public boolean isSystemDefault();
+    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_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";
+    field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
+  }
+
+  public class TelecommManager {
+  }
+
+  public class VideoCallProfile implements android.os.Parcelable {
+    ctor public VideoCallProfile(int, int);
+    method public int describeContents();
+    method public int getQuality();
+    method public int getVideoState();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int QUALITY_DEFAULT = 4; // 0x4
+    field public static final int QUALITY_HIGH = 1; // 0x1
+    field public static final int QUALITY_LOW = 3; // 0x3
+    field public static final int QUALITY_MEDIUM = 2; // 0x2
+    field public static final int VIDEO_STATE_AUDIO_ONLY = 0; // 0x0
+    field public static final int VIDEO_STATE_BIDIRECTIONAL = 3; // 0x3
+    field public static final int VIDEO_STATE_PAUSED = 4; // 0x4
+    field public static final int VIDEO_STATE_RX_ENABLED = 2; // 0x2
+    field public static final int VIDEO_STATE_TX_ENABLED = 1; // 0x1
+  }
+
+}
+
 package android.telephony {
 
   public final class CellIdentityCdma implements android.os.Parcelable {
@@ -26994,6 +28134,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);
@@ -27234,6 +28423,7 @@
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
     method public java.lang.String getSubscriberId();
+    method public java.util.List<android.telecomm.Subscription> getSubscriptions();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public boolean hasIccCard();
@@ -27258,6 +28448,7 @@
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
+    field public static final java.lang.String EXTRA_SUBSCRIPTION = "subscription";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -29408,6 +30599,12 @@
     method public void captureStartValues(android.transition.TransitionValues);
   }
 
+  public class ChangeImageTransform extends android.transition.Transition {
+    ctor public ChangeImageTransform();
+    method public void captureEndValues(android.transition.TransitionValues);
+    method public void captureStartValues(android.transition.TransitionValues);
+  }
+
   public class ChangeTransform extends android.transition.Transition {
     ctor public ChangeTransform();
     method public void captureEndValues(android.transition.TransitionValues);
@@ -29431,12 +30628,6 @@
     field public static final int OUT = 2; // 0x2
   }
 
-  public class MoveImage extends android.transition.Transition {
-    ctor public MoveImage();
-    method public void captureEndValues(android.transition.TransitionValues);
-    method public void captureStartValues(android.transition.TransitionValues);
-  }
-
   public final class Scene {
     ctor public Scene(android.view.ViewGroup);
     ctor public Scene(android.view.ViewGroup, android.view.View);
@@ -29489,8 +30680,8 @@
     method public android.transition.TransitionPropagation getPropagation();
     method public long getStartDelay();
     method public java.util.List<java.lang.Integer> getTargetIds();
+    method public java.util.List<java.lang.String> getTargetNames();
     method public java.util.List<java.lang.Class> getTargetTypes();
-    method public java.util.List<java.lang.String> getTargetViewNames();
     method public java.util.List<android.view.View> getTargets();
     method public java.lang.String[] getTransitionProperties();
     method public android.transition.TransitionValues getTransitionValues(android.view.View, boolean);
@@ -29508,7 +30699,7 @@
     field public static final int MATCH_ID = 3; // 0x3
     field public static final int MATCH_INSTANCE = 1; // 0x1
     field public static final int MATCH_ITEM_ID = 4; // 0x4
-    field public static final int MATCH_VIEW_NAME = 2; // 0x2
+    field public static final int MATCH_NAME = 2; // 0x2
   }
 
   public static abstract class Transition.EpicenterCallback {
@@ -29575,6 +30766,9 @@
     method public android.animation.Animator onAppear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
     method public android.animation.Animator onDisappear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
     method public android.animation.Animator onDisappear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
+    method public void setMode(int);
+    field public static final int IN = 1; // 0x1
+    field public static final int OUT = 2; // 0x2
   }
 
   public abstract class VisibilityPropagation extends android.transition.TransitionPropagation {
@@ -30338,6 +31532,7 @@
   }
 
   public final class Display {
+    method public long getAppVsyncOffsetNanos();
     method public void getCurrentSizeRange(android.graphics.Point, android.graphics.Point);
     method public int getDisplayId();
     method public int getFlags();
@@ -30346,6 +31541,7 @@
     method public java.lang.String getName();
     method public deprecated int getOrientation();
     method public deprecated int getPixelFormat();
+    method public long getPresentationDeadlineNanos();
     method public void getRealMetrics(android.util.DisplayMetrics);
     method public void getRealSize(android.graphics.Point);
     method public void getRectSize(android.graphics.Rect);
@@ -31578,6 +32774,7 @@
     method public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method public void draw(android.graphics.Canvas);
+    method public void drawableHotspotChanged(float, float);
     method protected void drawableStateChanged();
     method public android.view.View findFocus();
     method public final android.view.View findViewById(int);
@@ -31593,6 +32790,8 @@
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
     method public android.graphics.drawable.Drawable getBackground();
+    method public android.content.res.ColorStateList getBackgroundTint();
+    method public android.graphics.PorterDuff.Mode getBackgroundTintMode();
     method public int getBaseline();
     method public final int getBottom();
     method protected float getBottomFadingEdgeStrength();
@@ -31694,13 +32893,13 @@
     method protected int getTopPaddingOffset();
     method public android.view.TouchDelegate getTouchDelegate();
     method public java.util.ArrayList<android.view.View> getTouchables();
+    method public java.lang.String getTransitionName();
     method public float getTranslationX();
     method public float getTranslationY();
     method public float getTranslationZ();
     method public int getVerticalFadingEdgeLength();
     method public int getVerticalScrollbarPosition();
     method public int getVerticalScrollbarWidth();
-    method public java.lang.String getViewName();
     method public android.view.ViewTreeObserver getViewTreeObserver();
     method public int getVisibility();
     method public final int getWidth();
@@ -31851,6 +33050,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>);
@@ -31869,6 +33069,8 @@
     method public void setBackgroundColor(int);
     method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setBackgroundResource(int);
+    method public void setBackgroundTint(android.content.res.ColorStateList);
+    method public void setBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public final void setBottom(int);
     method public void setCameraDistance(float);
     method public void setClickable(boolean);
@@ -31954,13 +33156,13 @@
     method public void setTextDirection(int);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
+    method public final void setTransitionName(java.lang.String);
     method public void setTranslationX(float);
     method public void setTranslationY(float);
     method public void setTranslationZ(float);
     method public void setVerticalFadingEdgeEnabled(boolean);
     method public void setVerticalScrollBarEnabled(boolean);
     method public void setVerticalScrollbarPosition(int);
-    method public final void setViewName(java.lang.String);
     method public void setVisibility(int);
     method public void setWillNotCacheDrawing(boolean);
     method public void setWillNotDraw(boolean);
@@ -32194,7 +33396,7 @@
     method public abstract boolean onTouch(android.view.View, android.view.MotionEvent);
   }
 
-  public class ViewAnimationUtils {
+  public final class ViewAnimationUtils {
     method public static final android.animation.ValueAnimator createCircularReveal(android.view.View, int, int, float, float);
   }
 
@@ -32659,6 +33861,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();
@@ -32717,6 +33920,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);
@@ -33718,7 +34922,7 @@
     ctor public CursorAnchorInfo(android.os.Parcel);
     method public int describeContents();
     method public android.graphics.RectF getCharacterRect(int);
-    method public java.lang.String getComposingText();
+    method public java.lang.CharSequence getComposingText();
     method public int getComposingTextStart();
     method public float getInsertionMarkerBaseline();
     method public float getInsertionMarkerBottom();
@@ -34145,16 +35349,21 @@
 
   public class CookieManager {
     method public synchronized boolean acceptCookie();
+    method public synchronized boolean acceptThirdPartyCookies(android.webkit.WebView);
     method public static boolean allowFileSchemeCookies();
     method public java.lang.String getCookie(java.lang.String);
     method public static synchronized android.webkit.CookieManager getInstance();
     method public synchronized boolean hasCookies();
-    method public void removeAllCookie();
-    method public void removeExpiredCookie();
-    method public void removeSessionCookie();
+    method public deprecated void removeAllCookie();
+    method public void removeAllCookies(android.webkit.ValueCallback<java.lang.Boolean>);
+    method public deprecated void removeExpiredCookie();
+    method public deprecated void removeSessionCookie();
+    method public void removeSessionCookies(android.webkit.ValueCallback<java.lang.Boolean>);
     method public synchronized void setAcceptCookie(boolean);
     method public static void setAcceptFileSchemeCookies(boolean);
+    method public synchronized void setAcceptThirdPartyCookies(android.webkit.WebView, boolean);
     method public void setCookie(java.lang.String, java.lang.String);
+    method public void setCookie(java.lang.String, java.lang.String, android.webkit.ValueCallback<java.lang.Boolean>);
   }
 
   public final class CookieSyncManager extends android.webkit.WebSyncManager {
@@ -34224,6 +35433,7 @@
     method public abstract long getResources();
     method public abstract void grant(long);
     field public static final long RESOURCE_AUDIO_CAPTURE = 4L; // 0x4L
+    field public static final long RESOURCE_PROTECTED_MEDIA_ID = 8L; // 0x8L
     field public static final long RESOURCE_VIDEO_CAPTURE = 2L; // 0x2L
   }
 
@@ -34296,12 +35506,25 @@
     method public void onRequestFocus(android.webkit.WebView);
     method public void onShowCustomView(android.view.View, android.webkit.WebChromeClient.CustomViewCallback);
     method public deprecated void onShowCustomView(android.view.View, int, android.webkit.WebChromeClient.CustomViewCallback);
+    method public boolean showFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams);
   }
 
   public static abstract interface WebChromeClient.CustomViewCallback {
     method public abstract void onCustomViewHidden();
   }
 
+  public static class WebChromeClient.FileChooserParams {
+    ctor public WebChromeClient.FileChooserParams();
+    field public static final int MODE_OPEN_FOLDER = 2; // 0x2
+    field public static final int MODE_OPEN_MULTIPLE = 1; // 0x1
+    field public static final int MODE_SAVE = 4; // 0x4
+    field public java.lang.String acceptTypes;
+    field public boolean capture;
+    field public java.lang.String defaultFilename;
+    field public int mode;
+    field public java.lang.String title;
+  }
+
   public class WebHistoryItem implements java.lang.Cloneable {
     method public android.graphics.Bitmap getFavicon();
     method public java.lang.String getOriginalUrl();
@@ -34817,10 +36040,14 @@
     method public boolean getSplitTrack();
     method public android.graphics.drawable.Drawable getThumb();
     method public int getThumbOffset();
+    method public android.content.res.ColorStateList getThumbTint();
+    method public android.graphics.PorterDuff.Mode getThumbTintMode();
     method public void setKeyProgressIncrement(int);
     method public void setSplitTrack(boolean);
     method public void setThumb(android.graphics.drawable.Drawable);
     method public void setThumbOffset(int);
+    method public void setThumbTint(android.content.res.ColorStateList);
+    method public void setThumbTintMode(android.graphics.PorterDuff.Mode);
   }
 
   public abstract class AbsSpinner extends android.widget.AdapterView {
@@ -35235,9 +36462,13 @@
     ctor public CompoundButton(android.content.Context, android.util.AttributeSet);
     ctor public CompoundButton(android.content.Context, android.util.AttributeSet, int);
     ctor public CompoundButton(android.content.Context, android.util.AttributeSet, int, int);
+    method public android.content.res.ColorStateList getButtonTint();
+    method public android.graphics.PorterDuff.Mode getButtonTintMode();
     method public boolean isChecked();
     method public void setButtonDrawable(int);
     method public void setButtonDrawable(android.graphics.drawable.Drawable);
+    method public void setButtonTint(android.content.res.ColorStateList);
+    method public void setButtonTintMode(android.graphics.PorterDuff.Mode);
     method public void setChecked(boolean);
     method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener);
     method public void toggle();
@@ -35359,12 +36590,14 @@
     ctor public EdgeEffect(android.content.Context);
     method public boolean draw(android.graphics.Canvas);
     method public void finish();
+    method public int getColor();
     method public int getMaxHeight();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
     method public void onPull(float, float);
     method public void onRelease();
+    method public void setColor(int);
     method public void setSize(int, int);
   }
 
@@ -35498,10 +36731,14 @@
     method public deprecated boolean getConsiderGoneChildrenWhenMeasuring();
     method public android.graphics.drawable.Drawable getForeground();
     method public int getForegroundGravity();
+    method public android.content.res.ColorStateList getForegroundTint();
+    method public android.graphics.PorterDuff.Mode getForegroundTintMode();
     method public boolean getMeasureAllChildren();
     method protected void onLayout(boolean, int, int, int, int);
     method public void setForeground(android.graphics.drawable.Drawable);
     method public void setForegroundGravity(int);
+    method public void setForegroundTint(android.content.res.ColorStateList);
+    method public void setForegroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setMeasureAllChildren(boolean);
   }
 
@@ -35559,6 +36796,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);
@@ -35706,6 +36947,8 @@
     method public int getMaxHeight();
     method public int getMaxWidth();
     method public android.widget.ImageView.ScaleType getScaleType();
+    method public android.content.res.ColorStateList getTint();
+    method public android.graphics.PorterDuff.Mode getTintMode();
     method public int[] onCreateDrawableState(int);
     method public void setAdjustViewBounds(boolean);
     method public deprecated void setAlpha(int);
@@ -35727,6 +36970,8 @@
     method public void setMaxHeight(int);
     method public void setMaxWidth(int);
     method public void setScaleType(android.widget.ImageView.ScaleType);
+    method public void setTint(android.content.res.ColorStateList);
+    method public void setTintMode(android.graphics.PorterDuff.Mode);
   }
 
   public static final class ImageView.ScaleType extends java.lang.Enum {
@@ -36099,11 +37344,19 @@
     ctor public ProgressBar(android.content.Context, android.util.AttributeSet, int);
     ctor public ProgressBar(android.content.Context, android.util.AttributeSet, int, int);
     method public android.graphics.drawable.Drawable getIndeterminateDrawable();
+    method public android.content.res.ColorStateList getIndeterminateTint();
+    method public android.graphics.PorterDuff.Mode getIndeterminateTintMode();
     method public android.view.animation.Interpolator getInterpolator();
     method public synchronized int getMax();
     method public synchronized int getProgress();
+    method public android.content.res.ColorStateList getProgressBackgroundTint();
+    method public android.graphics.PorterDuff.Mode getProgressBackgroundTintMode();
     method public android.graphics.drawable.Drawable getProgressDrawable();
+    method public android.content.res.ColorStateList getProgressTint();
+    method public android.graphics.PorterDuff.Mode getProgressTintMode();
     method public synchronized int getSecondaryProgress();
+    method public android.content.res.ColorStateList getSecondaryProgressTint();
+    method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
     method public final synchronized void incrementProgressBy(int);
     method public final synchronized void incrementSecondaryProgressBy(int);
     method public synchronized boolean isIndeterminate();
@@ -36112,13 +37365,21 @@
     method public synchronized void setIndeterminate(boolean);
     method public void setIndeterminateDrawable(android.graphics.drawable.Drawable);
     method public void setIndeterminateDrawableTiled(android.graphics.drawable.Drawable);
+    method public void setIndeterminateTint(android.content.res.ColorStateList);
+    method public void setIndeterminateTintMode(android.graphics.PorterDuff.Mode);
     method public void setInterpolator(android.content.Context, int);
     method public void setInterpolator(android.view.animation.Interpolator);
     method public synchronized void setMax(int);
     method public synchronized void setProgress(int);
+    method public void setProgressBackgroundTint(android.content.res.ColorStateList);
+    method public void setProgressBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setProgressDrawable(android.graphics.drawable.Drawable);
     method public void setProgressDrawableTiled(android.graphics.drawable.Drawable);
+    method public void setProgressTint(android.content.res.ColorStateList);
+    method public void setProgressTintMode(android.graphics.PorterDuff.Mode);
     method public synchronized void setSecondaryProgress(int);
+    method public void setSecondaryProgressTint(android.content.res.ColorStateList);
+    method public void setSecondaryProgressTintMode(android.graphics.PorterDuff.Mode);
   }
 
   public class QuickContactBadge extends android.widget.ImageView implements android.view.View.OnClickListener {
@@ -36853,6 +38114,7 @@
     method public float getShadowDx();
     method public float getShadowDy();
     method public float getShadowRadius();
+    method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
     method public static int getTextColor(android.content.Context, android.content.res.TypedArray, int);
     method public final android.content.res.ColorStateList getTextColors();
@@ -36946,6 +38208,7 @@
     method public void setScroller(android.widget.Scroller);
     method public void setSelectAllOnFocus(boolean);
     method public void setShadowLayer(float, float, float, int);
+    method public final void setShowSoftInputOnFocus(boolean);
     method public void setSingleLine();
     method public void setSingleLine(boolean);
     method public final void setSpannableFactory(android.text.Spannable.Factory);
@@ -37052,6 +38315,7 @@
     method public android.graphics.drawable.Drawable getLogo();
     method public java.lang.CharSequence getLogoDescription();
     method public android.view.Menu getMenu();
+    method public java.lang.CharSequence getNavigationContentDescription();
     method public android.graphics.drawable.Drawable getNavigationIcon();
     method public java.lang.CharSequence getSubtitle();
     method public java.lang.CharSequence getTitle();
@@ -37077,9 +38341,11 @@
     method public void setSubtitle(int);
     method public void setSubtitle(java.lang.CharSequence);
     method public void setSubtitleTextAppearance(android.content.Context, int);
+    method public void setSubtitleTextColor(int);
     method public void setTitle(int);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitleTextAppearance(android.content.Context, int);
+    method public void setTitleTextColor(int);
     method public boolean showOverflowMenu();
   }
 
@@ -37226,6 +38492,22 @@
 
 }
 
+package com.android.internal.telecomm {
+
+  public abstract interface RemoteServiceCallback implements android.os.IInterface {
+    method public abstract void onError() throws android.os.RemoteException;
+    method public abstract void onResult(java.util.List<android.content.ComponentName>, java.util.List<android.os.IBinder>) throws android.os.RemoteException;
+  }
+
+  public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback {
+    ctor public RemoteServiceCallback.Stub();
+    method public android.os.IBinder asBinder();
+    method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder);
+    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
+  }
+
+}
+
 package com.android.internal.util {
 
   public abstract interface Predicate {
@@ -47168,6 +48450,35 @@
     method public V replace(K, V);
   }
 
+  public class ConcurrentLinkedDeque extends java.util.AbstractCollection implements java.util.Deque java.io.Serializable {
+    ctor public ConcurrentLinkedDeque();
+    ctor public ConcurrentLinkedDeque(java.util.Collection<? extends E>);
+    method public void addFirst(E);
+    method public void addLast(E);
+    method public java.util.Iterator<E> descendingIterator();
+    method public E element();
+    method public E getFirst();
+    method public E getLast();
+    method public java.util.Iterator<E> iterator();
+    method public boolean offer(E);
+    method public boolean offerFirst(E);
+    method public boolean offerLast(E);
+    method public E peek();
+    method public E peekFirst();
+    method public E peekLast();
+    method public E poll();
+    method public E pollFirst();
+    method public E pollLast();
+    method public E pop();
+    method public void push(E);
+    method public E remove();
+    method public E removeFirst();
+    method public boolean removeFirstOccurrence(java.lang.Object);
+    method public E removeLast();
+    method public boolean removeLastOccurrence(java.lang.Object);
+    method public int size();
+  }
+
   public class ConcurrentLinkedQueue extends java.util.AbstractQueue implements java.util.Queue java.io.Serializable {
     ctor public ConcurrentLinkedQueue();
     ctor public ConcurrentLinkedQueue(java.util.Collection<? extends E>);
@@ -47408,6 +48719,94 @@
     method public static java.util.concurrent.ScheduledExecutorService unconfigurableScheduledExecutorService(java.util.concurrent.ScheduledExecutorService);
   }
 
+  public class ForkJoinPool extends java.util.concurrent.AbstractExecutorService {
+    ctor public ForkJoinPool();
+    ctor public ForkJoinPool(int);
+    ctor public ForkJoinPool(int, java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory, java.lang.Thread.UncaughtExceptionHandler, boolean);
+    method public boolean awaitQuiescence(long, java.util.concurrent.TimeUnit);
+    method public boolean awaitTermination(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException;
+    method protected int drainTasksTo(java.util.Collection<? super java.util.concurrent.ForkJoinTask<?>>);
+    method public void execute(java.util.concurrent.ForkJoinTask<?>);
+    method public void execute(java.lang.Runnable);
+    method public int getActiveThreadCount();
+    method public boolean getAsyncMode();
+    method public java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory getFactory();
+    method public int getParallelism();
+    method public int getPoolSize();
+    method public int getQueuedSubmissionCount();
+    method public long getQueuedTaskCount();
+    method public int getRunningThreadCount();
+    method public long getStealCount();
+    method public java.lang.Thread.UncaughtExceptionHandler getUncaughtExceptionHandler();
+    method public boolean hasQueuedSubmissions();
+    method public T invoke(java.util.concurrent.ForkJoinTask<T>);
+    method public boolean isQuiescent();
+    method public boolean isShutdown();
+    method public boolean isTerminated();
+    method public boolean isTerminating();
+    method public static void managedBlock(java.util.concurrent.ForkJoinPool.ManagedBlocker) throws java.lang.InterruptedException;
+    method protected java.util.concurrent.ForkJoinTask<?> pollSubmission();
+    method public void shutdown();
+    method public java.util.List<java.lang.Runnable> shutdownNow();
+    method public java.util.concurrent.ForkJoinTask<T> submit(java.util.concurrent.ForkJoinTask<T>);
+    field public static final java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory;
+  }
+
+  public static abstract interface ForkJoinPool.ForkJoinWorkerThreadFactory {
+    method public abstract java.util.concurrent.ForkJoinWorkerThread newThread(java.util.concurrent.ForkJoinPool);
+  }
+
+  public static abstract interface ForkJoinPool.ManagedBlocker {
+    method public abstract boolean block() throws java.lang.InterruptedException;
+    method public abstract boolean isReleasable();
+  }
+
+  public abstract class ForkJoinTask implements java.util.concurrent.Future java.io.Serializable {
+    ctor public ForkJoinTask();
+    method public static java.util.concurrent.ForkJoinTask<?> adapt(java.lang.Runnable);
+    method public static java.util.concurrent.ForkJoinTask<T> adapt(java.lang.Runnable, T);
+    method public static java.util.concurrent.ForkJoinTask<T> adapt(java.util.concurrent.Callable<? extends T>);
+    method public boolean cancel(boolean);
+    method public void complete(V);
+    method public void completeExceptionally(java.lang.Throwable);
+    method protected abstract boolean exec();
+    method public final java.util.concurrent.ForkJoinTask<V> fork();
+    method public final V get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
+    method public final V get(long, java.util.concurrent.TimeUnit) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+    method public final java.lang.Throwable getException();
+    method public static java.util.concurrent.ForkJoinPool getPool();
+    method public static int getQueuedTaskCount();
+    method public abstract V getRawResult();
+    method public static int getSurplusQueuedTaskCount();
+    method public static void helpQuiesce();
+    method public static boolean inForkJoinPool();
+    method public final V invoke();
+    method public static void invokeAll(java.util.concurrent.ForkJoinTask<?>, java.util.concurrent.ForkJoinTask<?>);
+    method public static void invokeAll(java.util.concurrent.ForkJoinTask<?>...);
+    method public static java.util.Collection<T> invokeAll(java.util.Collection<T>);
+    method public final boolean isCancelled();
+    method public final boolean isCompletedAbnormally();
+    method public final boolean isCompletedNormally();
+    method public final boolean isDone();
+    method public final V join();
+    method protected static java.util.concurrent.ForkJoinTask<?> peekNextLocalTask();
+    method protected static java.util.concurrent.ForkJoinTask<?> pollNextLocalTask();
+    method protected static java.util.concurrent.ForkJoinTask<?> pollTask();
+    method public final void quietlyInvoke();
+    method public final void quietlyJoin();
+    method public void reinitialize();
+    method protected abstract void setRawResult(V);
+    method public boolean tryUnfork();
+  }
+
+  public class ForkJoinWorkerThread extends java.lang.Thread {
+    ctor protected ForkJoinWorkerThread(java.util.concurrent.ForkJoinPool);
+    method public java.util.concurrent.ForkJoinPool getPool();
+    method public int getPoolIndex();
+    method protected void onStart();
+    method protected void onTermination(java.lang.Throwable);
+  }
+
   public abstract interface Future {
     method public abstract boolean cancel(boolean);
     method public abstract V get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
@@ -47492,6 +48891,52 @@
     method public E take() throws java.lang.InterruptedException;
   }
 
+  public class LinkedTransferQueue extends java.util.AbstractQueue implements java.io.Serializable java.util.concurrent.TransferQueue {
+    ctor public LinkedTransferQueue();
+    ctor public LinkedTransferQueue(java.util.Collection<? extends E>);
+    method public int drainTo(java.util.Collection<? super E>);
+    method public int drainTo(java.util.Collection<? super E>, int);
+    method public int getWaitingConsumerCount();
+    method public boolean hasWaitingConsumer();
+    method public java.util.Iterator<E> iterator();
+    method public boolean offer(E, long, java.util.concurrent.TimeUnit);
+    method public boolean offer(E);
+    method public E peek();
+    method public E poll(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException;
+    method public E poll();
+    method public void put(E);
+    method public int remainingCapacity();
+    method public int size();
+    method public E take() throws java.lang.InterruptedException;
+    method public void transfer(E) throws java.lang.InterruptedException;
+    method public boolean tryTransfer(E);
+    method public boolean tryTransfer(E, long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException;
+  }
+
+  public class Phaser {
+    ctor public Phaser();
+    ctor public Phaser(int);
+    ctor public Phaser(java.util.concurrent.Phaser);
+    ctor public Phaser(java.util.concurrent.Phaser, int);
+    method public int arrive();
+    method public int arriveAndAwaitAdvance();
+    method public int arriveAndDeregister();
+    method public int awaitAdvance(int);
+    method public int awaitAdvanceInterruptibly(int) throws java.lang.InterruptedException;
+    method public int awaitAdvanceInterruptibly(int, long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+    method public int bulkRegister(int);
+    method public void forceTermination();
+    method public int getArrivedParties();
+    method public java.util.concurrent.Phaser getParent();
+    method public final int getPhase();
+    method public int getRegisteredParties();
+    method public java.util.concurrent.Phaser getRoot();
+    method public int getUnarrivedParties();
+    method public boolean isTerminated();
+    method protected boolean onAdvance(int, int);
+    method public int register();
+  }
+
   public class PriorityBlockingQueue extends java.util.AbstractQueue implements java.util.concurrent.BlockingQueue java.io.Serializable {
     ctor public PriorityBlockingQueue();
     ctor public PriorityBlockingQueue(int);
@@ -47512,6 +48957,22 @@
     method public E take() throws java.lang.InterruptedException;
   }
 
+  public abstract class RecursiveAction extends java.util.concurrent.ForkJoinTask {
+    ctor public RecursiveAction();
+    method protected abstract void compute();
+    method protected final boolean exec();
+    method public final java.lang.Void getRawResult();
+    method protected final void setRawResult(java.lang.Void);
+  }
+
+  public abstract class RecursiveTask extends java.util.concurrent.ForkJoinTask {
+    ctor public RecursiveTask();
+    method protected abstract V compute();
+    method protected final boolean exec();
+    method public final V getRawResult();
+    method protected final void setRawResult(V);
+  }
+
   public class RejectedExecutionException extends java.lang.RuntimeException {
     ctor public RejectedExecutionException();
     ctor public RejectedExecutionException(java.lang.String);
@@ -47550,12 +49011,14 @@
     method protected java.util.concurrent.RunnableScheduledFuture<V> decorateTask(java.util.concurrent.Callable<V>, java.util.concurrent.RunnableScheduledFuture<V>);
     method public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy();
     method public boolean getExecuteExistingDelayedTasksAfterShutdownPolicy();
+    method public boolean getRemoveOnCancelPolicy();
     method public java.util.concurrent.ScheduledFuture<?> schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit);
     method public java.util.concurrent.ScheduledFuture<V> schedule(java.util.concurrent.Callable<V>, long, java.util.concurrent.TimeUnit);
     method public java.util.concurrent.ScheduledFuture<?> scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit);
     method public java.util.concurrent.ScheduledFuture<?> scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit);
     method public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean);
     method public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean);
+    method public void setRemoveOnCancelPolicy(boolean);
   }
 
   public class Semaphore implements java.io.Serializable {
@@ -47601,6 +49064,15 @@
     method public abstract java.lang.Thread newThread(java.lang.Runnable);
   }
 
+  public class ThreadLocalRandom extends java.util.Random {
+    method public static java.util.concurrent.ThreadLocalRandom current();
+    method public double nextDouble(double);
+    method public double nextDouble(double, double);
+    method public int nextInt(int, int);
+    method public long nextLong(long);
+    method public long nextLong(long, long);
+  }
+
   public class ThreadPoolExecutor extends java.util.concurrent.AbstractExecutorService {
     ctor public ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue<java.lang.Runnable>);
     ctor public ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue<java.lang.Runnable>, java.util.concurrent.ThreadFactory);
@@ -47688,6 +49160,14 @@
     ctor public TimeoutException(java.lang.String);
   }
 
+  public abstract interface TransferQueue implements java.util.concurrent.BlockingQueue {
+    method public abstract int getWaitingConsumerCount();
+    method public abstract boolean hasWaitingConsumer();
+    method public abstract void transfer(E) throws java.lang.InterruptedException;
+    method public abstract boolean tryTransfer(E);
+    method public abstract boolean tryTransfer(E, long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException;
+  }
+
 }
 
 package java.util.concurrent.atomic {
@@ -47897,6 +49377,7 @@
     method public final int getWaitQueueLength(java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject);
     method public final java.util.Collection<java.lang.Thread> getWaitingThreads(java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject);
     method public final boolean hasContended();
+    method public final boolean hasQueuedPredecessors();
     method public final boolean hasQueuedThreads();
     method public final boolean hasWaiters(java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject);
     method protected boolean isHeldExclusively();
@@ -47943,6 +49424,7 @@
     method public final int getWaitQueueLength(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject);
     method public final java.util.Collection<java.lang.Thread> getWaitingThreads(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject);
     method public final boolean hasContended();
+    method public final boolean hasQueuedPredecessors();
     method public final boolean hasQueuedThreads();
     method public final boolean hasWaiters(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject);
     method protected boolean isHeldExclusively();
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..127b0fc 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -32,9 +32,11 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 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;
@@ -42,10 +44,15 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.AndroidException;
 import android.view.IWindowManager;
+import android.view.View;
+
 import com.android.internal.os.BaseCommand;
 
+import dalvik.system.VMRuntime;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -94,7 +101,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" +
@@ -116,6 +127,7 @@
                 "       am stack info <STACK_ID>\n" +
                 "       am lock-task <TASK_ID>\n" +
                 "       am lock-task stop\n" +
+                "       am get-config\n" +
                 "\n" +
                 "am start: start an Activity.  Options are:\n" +
                 "    -D: enable debugging\n" +
@@ -222,6 +234,9 @@
                 "\n" +
                 "am lock-task: bring <TASK_ID> to the front and don't allow other tasks to run\n" +
                 "\n" +
+                "am get-config: retrieve the configuration and any recent configurations\n" +
+                "  of the device\n" +
+                "\n" +
                 "<INTENT> specifications include these flags and arguments:\n" +
                 "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
                 "    [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
@@ -316,6 +331,8 @@
             runStack();
         } else if (op.equals("lock-task")) {
             runLockTask();
+        } else if (op.equals("get-config")) {
+            runGetConfig();
         } else {
             showError("Error: unknown command '" + op + "'");
         }
@@ -835,6 +852,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 +871,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 +903,23 @@
             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());
         }
 
@@ -1672,4 +1708,19 @@
         } catch (RemoteException e) {
         }
     }
+
+    private void runGetConfig() throws Exception {
+        try {
+            Configuration config = mAm.getConfiguration();
+            if (config == null) {
+                System.err.println("Activity manager has no configuration");
+                return;
+            }
+
+            System.out.println("config: " + Configuration.resourceQualifierString(config));
+            System.out.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
+
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index db3d8bb..d683851 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 
 public final class Bmgr {
@@ -102,6 +103,11 @@
             return;
         }
 
+        if ("fullbackup".equals(op)) {
+            doFullTransportBackup();
+            return;
+        }
+
         System.err.println("Unknown command");
         showUsage();
     }
@@ -165,6 +171,24 @@
         }
     }
 
+    private void doFullTransportBackup() {
+        System.out.println("Performing full transport backup");
+
+        String pkg;
+        ArrayList<String> allPkgs = new ArrayList<String>();
+        while ((pkg = nextArg()) != null) {
+            allPkgs.add(pkg);
+        }
+        if (allPkgs.size() > 0) {
+            try {
+                mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()]));
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(BMGR_NOT_RUNNING_ERR);
+            }
+        }
+    }
+
     private void doTransport() {
         try {
             String which = nextArg();
@@ -453,6 +477,7 @@
         System.err.println("       bmgr restore PACKAGE");
         System.err.println("       bmgr run");
         System.err.println("       bmgr wipe TRANSPORT PACKAGE");
+        System.err.println("       bmgr fullbackup PACKAGE...");
         System.err.println("");
         System.err.println("The 'backup' command schedules a backup pass for the named package.");
         System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -468,11 +493,11 @@
         System.err.println("");
         System.err.println("The 'list transports' command reports the names of the backup transports");
         System.err.println("currently available on the device.  These names can be passed as arguments");
-        System.err.println("to the 'transport' and 'wipe' commands.  The currently selected transport");
+        System.err.println("to the 'transport' and 'wipe' commands.  The currently active transport");
         System.err.println("is indicated with a '*' character.");
         System.err.println("");
         System.err.println("The 'list sets' command reports the token and name of each restore set");
-        System.err.println("available to the device via the current transport.");
+        System.err.println("available to the device via the currently active transport.");
         System.err.println("");
         System.err.println("The 'transport' command designates the named transport as the currently");
         System.err.println("active one.  This setting is persistent across reboots.");
@@ -500,5 +525,8 @@
         System.err.println("erased from the given transport's storage.  The next backup operation");
         System.err.println("that the given application performs will rewrite its entire data set.");
         System.err.println("Transport names to use here are those reported by 'list transports'.");
+        System.err.println("");
+        System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
+        System.err.println("packages.  The data is sent via the currently active transport.");
     }
 }
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 2673031..ffc0f87 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 + "'");
         }
@@ -70,6 +69,7 @@
         boolean doEverything = false;
         boolean doWidgets = false;
         boolean allIncludesSystem = true;
+        boolean doCompress = true;
 
         String arg;
         while ((arg = nextArg()) != null) {
@@ -96,6 +96,10 @@
                     doWidgets = false;
                 } else if ("-all".equals(arg)) {
                     doEverything = true;
+                } else if ("-compress".equals(arg)) {
+                    doCompress = true;
+                } else if ("-nocompress".equals(arg)) {
+                    doCompress = false;
                 } else {
                     Log.w(TAG, "Unknown backup flag " + arg);
                     continue;
@@ -120,7 +124,7 @@
             fd = ParcelFileDescriptor.adoptFd(socketFd);
             String[] packArray = new String[packages.size()];
             mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
-                    doEverything, allIncludesSystem, packages.toArray(packArray));
+                    doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for backup");
         } finally {
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index ae35f7b..593a197 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -105,7 +105,7 @@
 
         uint32_t cached_target_crc, cached_overlay_crc;
         String8 cached_target_path, cached_overlay_path;
-        if (!ResTable::getIdmapInfo(buf, N, &cached_target_crc, &cached_overlay_crc,
+        if (!ResTable::getIdmapInfo(buf, N, NULL, &cached_target_crc, &cached_overlay_crc,
                     &cached_target_path, &cached_overlay_path)) {
             return true;
         }
diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
index 46c0edc..90cfa2c 100644
--- a/cmds/idmap/idmap.cpp
+++ b/cmds/idmap/idmap.cpp
@@ -66,26 +66,32 @@
       Display an idmap file: \n\
 \n\
       $ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\
-      SECTION      ENTRY        VALUE      OFFSET    COMMENT \n\
-      IDMAP HEADER magic        0x706d6469 0x0 \n\
-                   base crc     0x484aa77f 0x1 \n\
-                   overlay crc  0x03c66fa5 0x2 \n\
-                   base path    .......... 0x03-0x42 /system/app/target.apk \n\
-                   overlay path .......... 0x43-0x82 /vendor/overlay/overlay.apk \n\
-      DATA HEADER  types count  0x00000003 0x83 \n\
-                   padding      0x00000000 0x84 \n\
-                   type offset  0x00000004 0x85      absolute offset 0x87, xml \n\
-                   type offset  0x00000007 0x86      absolute offset 0x8a, string \n\
-      DATA BLOCK   entry count  0x00000001 0x87 \n\
-                   entry offset 0x00000000 0x88 \n\
-                   entry        0x7f020000 0x89      xml/integer \n\
-      DATA BLOCK   entry count  0x00000002 0x8a \n\
-                   entry offset 0x00000000 0x8b \n\
-                   entry        0x7f030000 0x8c      string/str \n\
-                   entry        0x7f030001 0x8d      string/str2 \n\
+      SECTION      ENTRY        VALUE      COMMENT \n\
+      IDMAP HEADER magic        0x706d6469 \n\
+                   base crc     0xb65a383f \n\
+                   overlay crc  0x7b9675e8 \n\
+                   base path    .......... /path/to/target.apk \n\
+                   overlay path .......... /path/to/overlay.apk \n\
+      DATA HEADER  target pkg   0x0000007f \n\
+                   types count  0x00000003 \n\
+      DATA BLOCK   target type  0x00000002 \n\
+                   overlay type 0x00000002 \n\
+                   entry count  0x00000001 \n\
+                   entry offset 0x00000000 \n\
+                   entry        0x00000000 drawable/drawable \n\
+      DATA BLOCK   target type  0x00000003 \n\
+                   overlay type 0x00000003 \n\
+                   entry count  0x00000001 \n\
+                   entry offset 0x00000000 \n\
+                   entry        0x00000000 xml/integer \n\
+      DATA BLOCK   target type  0x00000004 \n\
+                   overlay type 0x00000004 \n\
+                   entry count  0x00000001 \n\
+                   entry offset 0x00000000 \n\
+                   entry        0x00000000 raw/lorem_ipsum \n\
 \n\
       In this example, the overlay package provides three alternative resource values:\n\
-      xml/integer, string/str and string/str2.\n\
+      drawable/drawable, xml/integer, and raw/lorem_ipsum \n\
 \n\
 NOTES \n\
       This tool and its expected invocation from installd is modelled on dexopt.";
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
index a59f5d3..b9ac8a5 100644
--- a/cmds/idmap/inspect.cpp
+++ b/cmds/idmap/inspect.cpp
@@ -10,92 +10,108 @@
 
 using namespace android;
 
-#define NEXT(b, i, o) do { if (buf.next(&i, &o) < 0) { return -1; } } while (0)
-
 namespace {
-    static const uint32_t IDMAP_MAGIC = 0x706d6469;
+    static const uint32_t IDMAP_MAGIC = 0x504D4449;
     static const size_t PATH_LENGTH = 256;
-    static const uint32_t IDMAP_HEADER_SIZE = (3 + 2 * (PATH_LENGTH / sizeof(uint32_t)));
 
     void printe(const char *fmt, ...);
 
     class IdmapBuffer {
         private:
-            char *buf_;
+            const char* buf_;
             size_t len_;
-            mutable size_t pos_;
+            size_t pos_;
         public:
-            IdmapBuffer() : buf_((char *)MAP_FAILED), len_(0), pos_(0) {}
+            IdmapBuffer() : buf_((const char *)MAP_FAILED), len_(0), pos_(0) {}
 
             ~IdmapBuffer() {
                 if (buf_ != MAP_FAILED) {
-                    munmap(buf_, len_);
+                    munmap(const_cast<char*>(buf_), len_);
                 }
             }
 
-            int init(const char *idmap_path)
-            {
+            status_t init(const char *idmap_path) {
                 struct stat st;
                 int fd;
 
                 if (stat(idmap_path, &st) < 0) {
                     printe("failed to stat idmap '%s': %s\n", idmap_path, strerror(errno));
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
                 len_ = st.st_size;
                 if ((fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY))) < 0) {
                     printe("failed to open idmap '%s': %s\n", idmap_path, strerror(errno));
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
-                if ((buf_ = (char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
+                if ((buf_ = (const char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
                     close(fd);
                     printe("failed to mmap idmap: %s\n", strerror(errno));
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
                 close(fd);
-                return 0;
+                return NO_ERROR;
             }
 
-            int next(uint32_t *i, uint32_t *offset) const
-            {
+            status_t nextUint32(uint32_t* i) {
                 if (!buf_) {
                     printe("failed to read next uint32_t: buffer not initialized\n");
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
-                if (pos_ + 4 > len_) {
+
+                if (pos_ + sizeof(uint32_t) > len_) {
                     printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n",
                             pos_);
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
-                *offset = pos_ / sizeof(uint32_t);
-                char a = buf_[pos_++];
-                char b = buf_[pos_++];
-                char c = buf_[pos_++];
-                char d = buf_[pos_++];
-                *i = (d << 24) | (c << 16) | (b << 8) | a;
-                return 0;
+
+                if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x3) != 0) {
+                    printe("failed to read next uint32_t: not aligned on 4-byte boundary\n");
+                    return UNKNOWN_ERROR;
+                }
+
+                *i = dtohl(*reinterpret_cast<const uint32_t*>(buf_ + pos_));
+                pos_ += sizeof(uint32_t);
+                return NO_ERROR;
             }
 
-            int nextPath(char *b, uint32_t *offset_start, uint32_t *offset_end) const
-            {
+            status_t nextUint16(uint16_t* i) {
+                if (!buf_) {
+                    printe("failed to read next uint16_t: buffer not initialized\n");
+                    return UNKNOWN_ERROR;
+                }
+
+                if (pos_ + sizeof(uint16_t) > len_) {
+                    printe("failed to read next uint16_t: end of buffer reached at pos=0x%08x\n",
+                            pos_);
+                    return UNKNOWN_ERROR;
+                }
+
+                if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x1) != 0) {
+                    printe("failed to read next uint32_t: not aligned on 2-byte boundary\n");
+                    return UNKNOWN_ERROR;
+                }
+
+                *i = dtohs(*reinterpret_cast<const uint16_t*>(buf_ + pos_));
+                pos_ += sizeof(uint16_t);
+                return NO_ERROR;
+            }
+
+            status_t nextPath(char *b) {
                 if (!buf_) {
                     printe("failed to read next path: buffer not initialized\n");
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
                 if (pos_ + PATH_LENGTH > len_) {
                     printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_);
-                    return -1;
+                    return UNKNOWN_ERROR;
                 }
                 memcpy(b, buf_ + pos_, PATH_LENGTH);
-                *offset_start = pos_ / sizeof(uint32_t);
                 pos_ += PATH_LENGTH;
-                *offset_end = pos_ / sizeof(uint32_t) - 1;
-                return 0;
+                return NO_ERROR;
             }
     };
 
-    void printe(const char *fmt, ...)
-    {
+    void printe(const char *fmt, ...) {
         va_list ap;
 
         va_start(ap, fmt);
@@ -104,44 +120,37 @@
         va_end(ap);
     }
 
-    void print_header()
-    {
-        printf("SECTION      ENTRY        VALUE      OFFSET    COMMENT\n");
+    void print_header() {
+        printf("SECTION      ENTRY        VALUE      COMMENT\n");
     }
 
-    void print(const char *section, const char *subsection, uint32_t value, uint32_t offset,
-            const char *fmt, ...)
-    {
+    void print(const char *section, const char *subsection, uint32_t value, const char *fmt, ...) {
         va_list ap;
 
         va_start(ap, fmt);
-        printf("%-12s %-12s 0x%08x 0x%-4x    ", section, subsection, value, offset);
+        printf("%-12s %-12s 0x%08x ", section, subsection, value);
         vprintf(fmt, ap);
         printf("\n");
         va_end(ap);
     }
 
-    void print_path(const char *section, const char *subsection, uint32_t offset_start,
-            uint32_t offset_end, const char *fmt, ...)
-    {
+    void print_path(const char *section, const char *subsection, const char *fmt, ...) {
         va_list ap;
 
         va_start(ap, fmt);
-        printf("%-12s %-12s .......... 0x%02x-0x%02x ", section, subsection, offset_start,
-                offset_end);
+        printf("%-12s %-12s .......... ", section, subsection);
         vprintf(fmt, ap);
         printf("\n");
         va_end(ap);
     }
 
-    int resource_metadata(const AssetManager& am, uint32_t res_id,
-            String8 *package, String8 *type, String8 *name)
-    {
+    status_t resource_metadata(const AssetManager& am, uint32_t res_id,
+            String8 *package, String8 *type, String8 *name) {
         const ResTable& rt = am.getResources();
         struct ResTable::resource_name data;
         if (!rt.getResourceName(res_id, false, &data)) {
             printe("failed to get resource name id=0x%08x\n", res_id);
-            return -1;
+            return UNKNOWN_ERROR;
         }
         if (package) {
             *package = String8(String16(data.package, data.packageLen));
@@ -152,140 +161,150 @@
         if (name) {
             *name = String8(String16(data.name, data.nameLen));
         }
-        return 0;
+        return NO_ERROR;
     }
 
-    int package_id(const AssetManager& am)
-    {
-        return (am.getResources().getBasePackageId(0)) << 24;
-    }
-
-    int parse_idmap_header(const IdmapBuffer& buf, AssetManager& am)
-    {
-        uint32_t i, o, e;
+    status_t parse_idmap_header(IdmapBuffer& buf, AssetManager& am) {
+        uint32_t i;
         char path[PATH_LENGTH];
 
-        NEXT(buf, i, o);
+        status_t err = buf.nextUint32(&i);
+        if (err != NO_ERROR) {
+            return err;
+        }
+
         if (i != IDMAP_MAGIC) {
             printe("not an idmap file: actual magic constant 0x%08x does not match expected magic "
                     "constant 0x%08x\n", i, IDMAP_MAGIC);
-            return -1;
+            return UNKNOWN_ERROR;
         }
+
         print_header();
-        print("IDMAP HEADER", "magic", i, o, "");
+        print("IDMAP HEADER", "magic", i, "");
 
-        NEXT(buf, i, o);
-        print("", "base crc", i, o, "");
-
-        NEXT(buf, i, o);
-        print("", "overlay crc", i, o, "");
-
-        if (buf.nextPath(path, &o, &e) < 0) {
-            // printe done from IdmapBuffer::nextPath
-            return -1;
+        err = buf.nextUint32(&i);
+        if (err != NO_ERROR) {
+            return err;
         }
-        print_path("", "base path", o, e, "%s", path);
+        print("", "version", i, "");
+
+        err = buf.nextUint32(&i);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        print("", "base crc", i, "");
+
+        err = buf.nextUint32(&i);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        print("", "overlay crc", i, "");
+
+        err = buf.nextPath(path);
+        if (err != NO_ERROR) {
+            // printe done from IdmapBuffer::nextPath
+            return err;
+        }
+        print_path("", "base path", "%s", path);
+
         if (!am.addAssetPath(String8(path), NULL)) {
             printe("failed to add '%s' as asset path\n", path);
-            return -1;
+            return UNKNOWN_ERROR;
         }
 
-        if (buf.nextPath(path, &o, &e) < 0) {
+        err = buf.nextPath(path);
+        if (err != NO_ERROR) {
             // printe done from IdmapBuffer::nextPath
-            return -1;
+            return err;
         }
-        print_path("", "overlay path", o, e, "%s", path);
+        print_path("", "overlay path", "%s", path);
 
-        return 0;
+        return NO_ERROR;
     }
 
-    int parse_data_header(const IdmapBuffer& buf, const AssetManager& am, Vector<uint32_t>& types)
-    {
-        uint32_t i, o;
-        const uint32_t numeric_package = package_id(am);
+    status_t parse_data(IdmapBuffer& buf, const AssetManager& am) {
+        const uint32_t packageId = am.getResources().getBasePackageId(0);
 
-        NEXT(buf, i, o);
-        print("DATA HEADER", "types count", i, o, "");
-        const uint32_t N = i;
+        uint16_t data16;
+        status_t err = buf.nextUint16(&data16);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        print("DATA HEADER", "target pkg", static_cast<uint32_t>(data16), "");
 
-        for (uint32_t j = 0; j < N; ++j) {
-            NEXT(buf, i, o);
-            if (i == 0) {
-                print("", "padding", i, o, "");
-            } else {
+        err = buf.nextUint16(&data16);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        print("", "types count", static_cast<uint32_t>(data16), "");
+
+        uint32_t typeCount = static_cast<uint32_t>(data16);
+        while (typeCount > 0) {
+            typeCount--;
+
+            err = buf.nextUint16(&data16);
+            if (err != NO_ERROR) {
+                return err;
+            }
+            const uint32_t targetTypeId = static_cast<uint32_t>(data16);
+            print("DATA BLOCK", "target type", targetTypeId, "");
+
+            err = buf.nextUint16(&data16);
+            if (err != NO_ERROR) {
+                return err;
+            }
+            print("", "overlay type", static_cast<uint32_t>(data16), "");
+
+            err = buf.nextUint16(&data16);
+            if (err != NO_ERROR) {
+                return err;
+            }
+            const uint32_t entryCount = static_cast<uint32_t>(data16);
+            print("", "entry count", entryCount, "");
+
+            err = buf.nextUint16(&data16);
+            if (err != NO_ERROR) {
+                return err;
+            }
+            const uint32_t entryOffset = static_cast<uint32_t>(data16);
+            print("", "entry offset", entryOffset, "");
+
+            for (uint32_t i = 0; i < entryCount; i++) {
+                uint32_t data32;
+                err = buf.nextUint32(&data32);
+                if (err != NO_ERROR) {
+                    return err;
+                }
+
+                uint32_t resID = (packageId << 24) | (targetTypeId << 16) | (entryOffset + i);
                 String8 type;
-                const uint32_t numeric_type = (j + 1) << 16;
-                const uint32_t res_id = numeric_package | numeric_type;
-                if (resource_metadata(am, res_id, NULL, &type, NULL) < 0) {
-                    // printe done from resource_metadata
-                    return -1;
+                String8 name;
+                err = resource_metadata(am, resID, NULL, &type, &name);
+                if (err != NO_ERROR) {
+                    return err;
                 }
-                print("", "type offset", i, o, "absolute offset 0x%02x, %s",
-                        i + IDMAP_HEADER_SIZE, type.string());
-                types.add(numeric_type);
+                print("", "entry", data32, "%s/%s", type.string(), name.string());
             }
         }
 
-        return 0;
-    }
-
-    int parse_data_block(const IdmapBuffer& buf, const AssetManager& am, size_t numeric_type)
-    {
-        uint32_t i, o, n, id_offset;
-        const uint32_t numeric_package = package_id(am);
-
-        NEXT(buf, i, o);
-        print("DATA BLOCK", "entry count", i, o, "");
-        n = i;
-
-        NEXT(buf, i, o);
-        print("", "entry offset", i, o, "");
-        id_offset = i;
-
-        for ( ; n > 0; --n) {
-            String8 type, name;
-
-            NEXT(buf, i, o);
-            if (i == 0) {
-                print("", "padding", i, o, "");
-            } else {
-                uint32_t res_id = numeric_package | numeric_type | id_offset;
-                if (resource_metadata(am, res_id, NULL, &type, &name) < 0) {
-                    // printe done from resource_metadata
-                    return -1;
-                }
-                print("", "entry", i, o, "%s/%s", type.string(), name.string());
-            }
-            ++id_offset;
-        }
-
-        return 0;
+        return NO_ERROR;
     }
 }
 
-int idmap_inspect(const char *idmap_path)
-{
+int idmap_inspect(const char *idmap_path) {
     IdmapBuffer buf;
     if (buf.init(idmap_path) < 0) {
         // printe done from IdmapBuffer::init
         return EXIT_FAILURE;
     }
     AssetManager am;
-    if (parse_idmap_header(buf, am) < 0) {
+    if (parse_idmap_header(buf, am) != NO_ERROR) {
         // printe done from parse_idmap_header
         return EXIT_FAILURE;
     }
-    Vector<uint32_t> types;
-    if (parse_data_header(buf, am, types) < 0) {
+    if (parse_data(buf, am) != NO_ERROR) {
         // printe done from parse_data_header
         return EXIT_FAILURE;
     }
-    const size_t N = types.size();
-    for (size_t i = 0; i < N; ++i) {
-        if (parse_data_block(buf, am, types.itemAt(i)) < 0) {
-            // printe done from parse_data_block
-            return EXIT_FAILURE;
-        }
-    }
     return EXIT_SUCCESS;
 }
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 92c6a51..4e91361 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -17,12 +17,19 @@
 
 package com.android.commands.media;
 
-import android.app.PendingIntent;
+import android.app.ActivityManager;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.media.IAudioService;
-import android.media.IRemoteControlDisplay;
+import android.media.MediaMetadata;
+import android.media.session.ISessionController;
+import android.media.session.ISessionManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionInfo;
+import android.media.session.PlaybackState;
+import android.media.session.RouteInfo;
 import android.os.Bundle;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -30,16 +37,17 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+
 import com.android.internal.os.BaseCommand;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
+import java.util.List;
 
 public class Media extends BaseCommand {
-
-    private IAudioService mAudioService;
+    private ISessionManager mSessionService;
 
     /**
      * Command-line entry point.
@@ -54,29 +62,35 @@
         out.println(
                 "usage: media [subcommand] [options]\n" +
                 "       media dispatch KEY\n" +
-                "       media remote-display\n" +
+                "       media list-sessions\n" +
+                "       media monitor <sessionId>\n" +
                 "\n" +
-                "media dispatch: dispatch a media key to the current media client.\n" +
+                "media dispatch: dispatch a media key to the system.\n" +
                 "                KEY may be: play, pause, play-pause, mute, headsethook,\n" +
-                "                stop, next, previous, rewind, recordm fast-forword.\n" +
-                "media remote-display: monitor remote display updates.\n"
+                "                stop, next, previous, rewind, record, fast-forword.\n" +
+                "media list-sessions: print a list of the current sessions.\n" +
+                        "media monitor: monitor updates to the specified session.\n" +
+                "                       Use the sessionId from list-sessions.\n"
         );
     }
 
     public void onRun() throws Exception {
-        mAudioService = IAudioService.Stub.asInterface(ServiceManager.checkService(
-                Context.AUDIO_SERVICE));
-        if (mAudioService == null) {
+        mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
+                Context.MEDIA_SESSION_SERVICE));
+        if (mSessionService == null) {
             System.err.println(NO_SYSTEM_ERROR_CODE);
-            throw new AndroidException("Can't connect to audio service; is the system running?");
+            throw new AndroidException(
+                    "Can't connect to media session service; is the system running?");
         }
 
         String op = nextArgRequired();
 
         if (op.equals("dispatch")) {
             runDispatch();
-        } else if (op.equals("remote-display")) {
-            runRemoteDisplay();
+        } else if (op.equals("list-sessions")) {
+            runListSessions();
+        } else if (op.equals("monitor")) {
+            runMonitor();
         } else {
             showError("Error: unknown command '" + op + "'");
             return;
@@ -85,11 +99,39 @@
 
     private void sendMediaKey(KeyEvent event) {
         try {
-            mAudioService.dispatchMediaKeyEvent(event);
+            mSessionService.dispatchMediaKeyEvent(event, false);
         } catch (RemoteException e) {
         }
     }
 
+    private void runMonitor() throws Exception {
+        String id = nextArgRequired();
+        if (id == null) {
+            showError("Error: must include a session id");
+            return;
+        }
+        boolean success = false;
+        try {
+            List<IBinder> sessions = mSessionService
+                    .getSessions(null, ActivityManager.getCurrentUser());
+            for (IBinder session : sessions) {
+                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                        .asInterface(session));
+                if (controller != null && controller.getSessionInfo().getId().equals(id)) {
+                    ControllerMonitor monitor = new ControllerMonitor(controller);
+                    monitor.run();
+                    success = true;
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            System.out.println("***Error monitoring session*** " + e.getMessage());
+        }
+        if (!success) {
+            System.out.println("No session found with id " + id);
+        }
+    }
+
     private void runDispatch() throws Exception {
         String cmd = nextArgRequired();
         int keycode;
@@ -127,65 +169,49 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
     }
 
-    class RemoteDisplayMonitor extends IRemoteControlDisplay.Stub {
-        RemoteDisplayMonitor() {
+    class ControllerMonitor extends MediaController.Callback {
+        private final MediaController mController;
+
+        public ControllerMonitor(MediaController controller) {
+            mController = controller;
         }
-
-
         @Override
-        public void setCurrentClientId(int clientGeneration, PendingIntent clientMediaIntent,
-                boolean clearing) {
-            System.out.println("New client: id=" + clientGeneration
-                    + " intent=" + clientMediaIntent + " clearing=" + clearing);
+        public void onSessionEvent(String event, Bundle extras) {
+            System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
         }
 
         @Override
-        public void setEnabled(boolean enabled) {
-            System.out.println("New enable state= " + (enabled ? "enabled" : "disabled"));
+        public void onRouteChanged(RouteInfo route) {
+            System.out.println("onRouteChanged " + route);
         }
 
         @Override
-        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
-                long currentPosMs, float speed) {
-            System.out.println("New state: id=" + generationId + " state=" + state
-                    + " time=" + stateChangeTimeMs + " pos=" + currentPosMs + " speed=" + speed);
+        public void onPlaybackStateChanged(PlaybackState state) {
+            System.out.println("onPlaybackStateChanged " + state);
         }
 
         @Override
-        public void setTransportControlInfo(int generationId, int transportControlFlags,
-                int posCapabilities) {
-            System.out.println("New control info: id=" + generationId
-                    + " flags=0x" + Integer.toHexString(transportControlFlags)
-                    + " cap=0x" + Integer.toHexString(posCapabilities));
-        }
-
-        @Override
-        public void setMetadata(int generationId, Bundle metadata) {
-            System.out.println("New metadata: id=" + generationId
-                    + " data=" + metadata);
-        }
-
-        @Override
-        public void setArtwork(int generationId, Bitmap artwork) {
-            System.out.println("New artwork: id=" + generationId
-                    + " art=" + artwork);
-        }
-
-        @Override
-        public void setAllMetadata(int generationId, Bundle metadata, Bitmap artwork) {
-            System.out.println("New metadata+artwork: id=" + generationId
-                    + " data=" + metadata + " art=" + artwork);
+        public void onMetadataChanged(MediaMetadata metadata) {
+            String mmString = metadata == null ? null : "title=" + metadata
+                    .getString(MediaMetadata.METADATA_KEY_TITLE);
+            System.out.println("onMetadataChanged " + mmString);
         }
 
         void printUsageMessage() {
-            System.out.println("Monitoring remote control displays...  available commands:");
+            System.out.println("V2Monitoring session " + mController.getSessionInfo().getId()
+                    + "...  available commands:");
             System.out.println("(q)uit: finish monitoring");
         }
 
         void run() throws RemoteException {
             printUsageMessage();
-
-            mAudioService.registerRemoteControlDisplay(this, 0, 0);
+            HandlerThread cbThread = new HandlerThread("MediaCb") {
+                @Override
+                protected void onLooperPrepared() {
+                    mController.addCallback(ControllerMonitor.this);
+                }
+            };
+            cbThread.start();
 
             try {
                 InputStreamReader converter = new InputStreamReader(System.in);
@@ -209,17 +235,35 @@
                         printUsageMessage();
                     }
                 }
-
             } catch (IOException e) {
                 e.printStackTrace();
             } finally {
-                mAudioService.unregisterRemoteControlDisplay(this);
+                cbThread.getLooper().quit();
+                try {
+                    mController.removeCallback(this);
+                } catch (Exception e) {
+                    // ignoring
+                }
             }
         }
     }
 
-    private void runRemoteDisplay() throws Exception {
-        RemoteDisplayMonitor monitor = new RemoteDisplayMonitor();
-        monitor.run();
+    private void runListSessions() {
+        System.out.println("Sessions:");
+        try {
+            List<IBinder> sessions = mSessionService
+                    .getSessions(null, ActivityManager.getCurrentUser());
+            for (IBinder session : sessions) {
+                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                        .asInterface(session));
+                if (controller != null) {
+                    MediaSessionInfo info = controller.getSessionInfo();
+                    System.out.println("  id=" + info.getId() + ", package="
+                            + info.getPackageName());
+                }
+            }
+        } catch (Exception e) {
+            System.out.println("***Error listing sessions***");
+        }
     }
 }
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 5454b46..f85a7dc 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;
@@ -63,6 +64,7 @@
 import javax.crypto.spec.SecretKeySpec;
 
 import com.android.internal.content.PackageHelper;
+import com.android.internal.util.ArrayUtils;
 
 public final class Pm {
     IPackageManager mPm;
@@ -823,6 +825,7 @@
         byte[] tag = null;
         String originatingUriString = null;
         String referrer = null;
+        String abi = null;
 
         while ((opt=nextOption()) != null) {
             if (opt.equals("-l")) {
@@ -893,12 +896,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 +1001,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 +1172,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 +1200,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 +1235,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) {
@@ -1487,6 +1550,12 @@
             if (info != null && info.applicationInfo != null) {
                 System.out.print("package:");
                 System.out.println(info.applicationInfo.sourceDir);
+                if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+                    for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+                        System.out.print("package:");
+                        System.out.println(splitSourceDir);
+                    }
+                }
             }
         } catch (RemoteException e) {
             System.err.println(e.toString());
@@ -1571,7 +1640,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 +1654,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..6b2a0e2 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -52,13 +52,13 @@
     );
 }
 
-static SkBitmap::Config flinger2skia(PixelFormat f)
+static SkColorType flinger2skia(PixelFormat f)
 {
     switch (f) {
         case PIXEL_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            return kRGB_565_SkColorType;
         default:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
     }
 }
 
@@ -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();
@@ -174,9 +174,10 @@
 
     if (base) {
         if (png) {
+            const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f),
+                                                       kPremul_SkAlphaType);
             SkBitmap b;
-            b.setConfig(flinger2skia(f), w, h, s*bytesPerPixel(f));
-            b.setPixels((void*)base);
+            b.installPixels(info, const_cast<void*>(base), s*bytesPerPixel(f));
             SkDynamicMemoryWStream stream;
             SkImageEncoder::EncodeStream(&stream, b,
                     SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality);
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/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 12fcdcf..806a55b 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -359,7 +359,29 @@
      */
     public AuthenticatorDescription[] getAuthenticatorTypes() {
         try {
-            return mService.getAuthenticatorTypes();
+            return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            // will never happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @hide
+     * Lists the currently registered authenticators for a given user id.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>The caller has to be in the same user or have the permission
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @return An array of {@link AuthenticatorDescription} for every
+     *     authenticator known to the AccountManager service.  Empty (never
+     *     null) if no authenticators are known.
+     */
+    public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+        try {
+            return mService.getAuthenticatorTypes(userId);
         } catch (RemoteException e) {
             // will never happen
             throw new RuntimeException(e);
@@ -389,6 +411,28 @@
 
     /**
      * @hide
+     * Lists all accounts of any type registered on the device for a given
+     * user id. Equivalent to getAccountsByType(null).
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#GET_ACCOUNTS}.
+     *
+     * @return An array of {@link Account}, one for each account.  Empty
+     *     (never null) if no accounts have been added.
+     */
+    public Account[] getAccountsAsUser(int userId) {
+        try {
+            return mService.getAccountsAsUser(null, userId);
+        } catch (RemoteException e) {
+            // won't ever happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @hide
      * For use by internal activities. Returns the list of accounts that the calling package
      * is authorized to use, particularly for shared accounts.
      * @param packageName package name of the calling app.
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 86e279f..1373dc8 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -29,7 +29,7 @@
 interface IAccountManager {
     String getPassword(in Account account);
     String getUserData(in Account account, String key);
-    AuthenticatorDescription[] getAuthenticatorTypes();
+    AuthenticatorDescription[] getAuthenticatorTypes(int userId);
     Account[] getAccounts(String accountType);
     Account[] getAccountsForPackage(String packageName, int uid);
     Account[] getAccountsByTypeForPackage(String type, String packageName);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 933135d..0f5e954 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -17,14 +17,22 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
+import android.graphics.Path;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.util.PathParser;
 import android.util.StateSet;
 import android.util.TypedValue;
 import android.util.Xml;
+import android.view.InflateException;
 import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -41,7 +49,7 @@
  * <em>something</em> file.)
  */
 public class AnimatorInflater {
-
+    private static final String TAG = "AnimatorInflater";
     /**
      * These flags are used when parsing AnimatorSet objects
      */
@@ -53,9 +61,12 @@
      */
     private static final int VALUE_TYPE_FLOAT       = 0;
     private static final int VALUE_TYPE_INT         = 1;
+    private static final int VALUE_TYPE_PATH        = 2;
     private static final int VALUE_TYPE_COLOR       = 4;
     private static final int VALUE_TYPE_CUSTOM      = 5;
 
+    private static final boolean DBG_ANIMATOR_INFLATER = false;
+
     /**
      * Loads an {@link Animator} object from a resource
      *
@@ -66,11 +77,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" +
@@ -139,7 +165,7 @@
                         int stateIndex = 0;
                         for (int i = 0; i < attributeCount; i++) {
                             int attrName = attributeSet.getAttributeNameResource(i);
-                            if (attrName == com.android.internal.R.attr.animation) {
+                            if (attrName == R.attr.animation) {
                                 animator = loadAnimator(context,
                                         attributeSet.getAttributeResourceValue(i, 0));
                             } else {
@@ -150,7 +176,8 @@
 
                         }
                         if (animator == null) {
-                            animator = createAnimatorFromXml(context, parser);
+                            animator = createAnimatorFromXml(context.getResources(),
+                                    context.getTheme(), parser);
                         }
 
                         if (animator == null) {
@@ -166,12 +193,302 @@
         }
     }
 
-    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
+    /**
+     * PathDataEvaluator is used to interpolate between two paths which are
+     * represented in the same format but different control points' values.
+     * The path is represented as an array of PathDataNode here, which is
+     * fundamentally an array of floating point numbers.
+     */
+    private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> {
+        private PathParser.PathDataNode[] mNodeArray;
+
+        /**
+         * Create a PathParser.PathDataNode[] that does not reuse the animated value.
+         * Care must be taken when using this option because on every evaluation
+         * a new <code>PathParser.PathDataNode[]</code> will be allocated.
+         */
+        private PathDataEvaluator() {}
+
+        /**
+         * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call.
+         * Caution must be taken to ensure that the value returned from
+         * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+         * used across threads. The value will be modified on each <code>evaluate()</code> call.
+         *
+         * @param nodeArray The array to modify and return from <code>evaluate</code>.
+         */
+        public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) {
+            mNodeArray = nodeArray;
+        }
+
+        @Override
+        public PathParser.PathDataNode[] evaluate(float fraction,
+                PathParser.PathDataNode[] startPathData,
+                PathParser.PathDataNode[] endPathData) {
+            if (!PathParser.canMorph(startPathData, endPathData)) {
+                throw new IllegalArgumentException("Can't interpolate between"
+                        + " two incompatible pathData");
+            }
+
+            if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) {
+                mNodeArray = PathParser.deepCopyNodes(startPathData);
+            }
+
+            for (int i = 0; i < startPathData.length; i++) {
+                mNodeArray[i].interpolatePathDataNode(startPathData[i],
+                        endPathData[i], fraction);
+            }
+
+            return mNodeArray;
+        }
     }
 
-    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
+    /**
+     * @param anim Null if this is a ValueAnimator, otherwise this is an
+     *            ObjectAnimator
+     * @param arrayAnimator Incoming typed array for Animator's attributes.
+     * @param arrayObjectAnimator Incoming typed array for Object Animator's
+     *            attributes.
+     */
+    private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+            TypedArray arrayAnimator, TypedArray arrayObjectAnimator) {
+        long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
+
+        long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
+
+        int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
+                VALUE_TYPE_FLOAT);
+
+        if (anim == null) {
+            anim = new ValueAnimator();
+        }
+
+        TypeEvaluator evaluator = null;
+
+        boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
+        TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom);
+        boolean hasFrom = (tvFrom != null);
+        int fromType = hasFrom ? tvFrom.type : 0;
+        TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo);
+        boolean hasTo = (tvTo != null);
+        int toType = hasTo ? tvTo.type : 0;
+
+        // TODO: Further clean up this part of code into 4 types : path, color,
+        // integer and float.
+        if (valueType == VALUE_TYPE_PATH) {
+            evaluator = setupAnimatorForPath(anim, arrayAnimator);
+        } else {
+            // Integer and float value types are handled here.
+            if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                    (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
+                    (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                            (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+                // special case for colors: ignore valueType and get ints
+                getFloats = false;
+                evaluator = ArgbEvaluator.getInstance();
+            }
+            setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType);
+        }
+
+        anim.setDuration(duration);
+        anim.setStartDelay(startDelay);
+
+        if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
+            anim.setRepeatCount(
+                    arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
+        }
+        if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
+            anim.setRepeatMode(
+                    arrayAnimator.getInt(R.styleable.Animator_repeatMode,
+                            ValueAnimator.RESTART));
+        }
+        if (evaluator != null) {
+            anim.setEvaluator(evaluator);
+        }
+
+        if (arrayObjectAnimator != null) {
+            setupObjectAnimator(anim, arrayObjectAnimator, getFloats);
+        }
+    }
+
+    /**
+     * Setup the Animator to achieve path morphing.
+     *
+     * @param anim The target Animator which will be updated.
+     * @param arrayAnimator TypedArray for the ValueAnimator.
+     * @return the PathDataEvaluator.
+     */
+    private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
+             TypedArray arrayAnimator) {
+        TypeEvaluator evaluator = null;
+        String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
+        String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
+        PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
+        PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+
+        if (nodesFrom != null) {
+            if (nodesTo != null) {
+                anim.setObjectValues(nodesFrom, nodesTo);
+                if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+                    throw new InflateException(arrayAnimator.getPositionDescription()
+                            + " Can't morph from " + fromString + " to " + toString);
+                }
+            } else {
+                anim.setObjectValues((Object)nodesFrom);
+            }
+            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+        } else if (nodesTo != null) {
+            anim.setObjectValues((Object)nodesTo);
+            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+        }
+
+        if (DBG_ANIMATOR_INFLATER && evaluator != null) {
+            Log.v(TAG, "create a new PathDataEvaluator here");
+        }
+
+        return evaluator;
+    }
+
+    /**
+     * Setup ObjectAnimator's property or values from pathData.
+     *
+     * @param anim The target Animator which will be updated.
+     * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
+     * @param getFloats True if the value type is float.
+     */
+    private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
+            boolean getFloats) {
+        ObjectAnimator oa = (ObjectAnimator) anim;
+        String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+        // Note that if there is a pathData defined in the Object Animator,
+        // valueFrom / valueTo will be ignored.
+        if (pathData != null) {
+            String propertyXName =
+                    arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+            String propertyYName =
+                    arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+            if (propertyXName == null && propertyYName == null) {
+                throw new InflateException(arrayObjectAnimator.getPositionDescription()
+                        + " propertyXName or propertyYName is needed for PathData");
+            } else {
+                Path path = PathParser.createPathFromPathData(pathData);
+                Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
+                PropertyValuesHolder x = null;
+                PropertyValuesHolder y = null;
+                if (propertyXName != null) {
+                    x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
+                }
+                if (propertyYName != null) {
+                    y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
+                }
+                if (x == null) {
+                    oa.setValues(y);
+                } else if (y == null) {
+                    oa.setValues(x);
+                } else {
+                    oa.setValues(x, y);
+                }
+            }
+        } else {
+            String propertyName =
+                    arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+            oa.setPropertyName(propertyName);
+        }
+    }
+
+    /**
+     * Setup ValueAnimator's values.
+     * This will handle all of the integer, float and color types.
+     *
+     * @param anim The target Animator which will be updated.
+     * @param arrayAnimator TypedArray for the ValueAnimator.
+     * @param getFloats True if the value type is float.
+     * @param hasFrom True if "valueFrom" exists.
+     * @param fromType The type of "valueFrom".
+     * @param hasTo True if "valueTo" exists.
+     * @param toType The type of "valueTo".
+     */
+    private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
+            boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
+        int valueFromIndex = R.styleable.Animator_valueFrom;
+        int valueToIndex = R.styleable.Animator_valueTo;
+        if (getFloats) {
+            float valueFrom;
+            float valueTo;
+            if (hasFrom) {
+                if (fromType == TypedValue.TYPE_DIMENSION) {
+                    valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
+                } else {
+                    valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
+                }
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+                    } else {
+                        valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+                    }
+                    anim.setFloatValues(valueFrom, valueTo);
+                } else {
+                    anim.setFloatValues(valueFrom);
+                }
+            } else {
+                if (toType == TypedValue.TYPE_DIMENSION) {
+                    valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+                } else {
+                    valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+                }
+                anim.setFloatValues(valueTo);
+            }
+        } else {
+            int valueFrom;
+            int valueTo;
+            if (hasFrom) {
+                if (fromType == TypedValue.TYPE_DIMENSION) {
+                    valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
+                } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                        (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                    valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
+                } else {
+                    valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
+                }
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        valueTo = arrayAnimator.getColor(valueToIndex, 0);
+                    } else {
+                        valueTo = arrayAnimator.getInt(valueToIndex, 0);
+                    }
+                    anim.setIntValues(valueFrom, valueTo);
+                } else {
+                    anim.setIntValues(valueFrom);
+                }
+            } else {
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        valueTo = arrayAnimator.getColor(valueToIndex, 0);
+                    } else {
+                        valueTo = arrayAnimator.getInt(valueToIndex, 0);
+                    }
+                    anim.setIntValues(valueTo);
+                }
+            }
+        }
+    }
+
+    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 {
 
@@ -182,26 +499,30 @@
         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;
             }
 
-            String  name = parser.getName();
+            String name = parser.getName();
 
             if (name.equals("objectAnimator")) {
-                anim = loadObjectAnimator(c, attrs);
+                anim = loadObjectAnimator(res, theme, attrs);
             } else if (name.equals("animator")) {
-                anim = loadAnimator(c, attrs, null);
+                anim = loadAnimator(res, theme, 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,
+                TypedArray a;
+                if (theme != null) {
+                    a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
+                } else {
+                    a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
+                }
+                int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
                         TOGETHER);
-                createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
+                createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering);
                 a.recycle();
             } else {
                 throw new RuntimeException("Unknown animator name: " + parser.getName());
@@ -231,160 +552,56 @@
 
     }
 
-    private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
+    private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, 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();
+        loadAnimator(res, theme, attrs, anim);
 
         return anim;
     }
 
     /**
-     * Creates a new animation whose parameters come from the specified context and
-     * attributes set.
+     * 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
+     * @param res The resources
+     * @param attrs The set of attributes holding the animation parameters
+     * @param anim Null if this is a ValueAnimator, otherwise this is an
+     *            ObjectAnimator
      */
-    private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
+    private static ValueAnimator loadAnimator(Resources res, Theme theme,
+            AttributeSet attrs, ValueAnimator anim)
             throws NotFoundException {
 
-        TypedArray a =
-                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
+        TypedArray arrayAnimator = null;
+        TypedArray arrayObjectAnimator = null;
 
-        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
-
-        long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
-
-        int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
-                VALUE_TYPE_FLOAT);
-
-        if (anim == null) {
-            anim = new ValueAnimator();
-        }
-        TypeEvaluator evaluator = null;
-
-        int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom;
-        int valueToIndex = com.android.internal.R.styleable.Animator_valueTo;
-
-        boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
-
-        TypedValue tvFrom = a.peekValue(valueFromIndex);
-        boolean hasFrom = (tvFrom != null);
-        int fromType = hasFrom ? tvFrom.type : 0;
-        TypedValue tvTo = a.peekValue(valueToIndex);
-        boolean hasTo = (tvTo != null);
-        int toType = hasTo ? tvTo.type : 0;
-
-        if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
-            (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
-            // special case for colors: ignore valueType and get ints
-            getFloats = false;
-            evaluator = ArgbEvaluator.getInstance();
-        }
-
-        if (getFloats) {
-            float valueFrom;
-            float valueTo;
-            if (hasFrom) {
-                if (fromType == TypedValue.TYPE_DIMENSION) {
-                    valueFrom = a.getDimension(valueFromIndex, 0f);
-                } else {
-                    valueFrom = a.getFloat(valueFromIndex, 0f);
-                }
-                if (hasTo) {
-                    if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = a.getDimension(valueToIndex, 0f);
-                    } else {
-                        valueTo = a.getFloat(valueToIndex, 0f);
-                    }
-                    anim.setFloatValues(valueFrom, valueTo);
-                } else {
-                    anim.setFloatValues(valueFrom);
-                }
-            } else {
-                if (toType == TypedValue.TYPE_DIMENSION) {
-                    valueTo = a.getDimension(valueToIndex, 0f);
-                } else {
-                    valueTo = a.getFloat(valueToIndex, 0f);
-                }
-                anim.setFloatValues(valueTo);
-            }
+        if (theme != null) {
+            arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
         } else {
-            int valueFrom;
-            int valueTo;
-            if (hasFrom) {
-                if (fromType == TypedValue.TYPE_DIMENSION) {
-                    valueFrom = (int) a.getDimension(valueFromIndex, 0f);
-                } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                        (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                    valueFrom = a.getColor(valueFromIndex, 0);
-                } else {
-                    valueFrom = a.getInt(valueFromIndex, 0);
-                }
-                if (hasTo) {
-                    if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = (int) a.getDimension(valueToIndex, 0f);
-                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                        valueTo = a.getColor(valueToIndex, 0);
-                    } else {
-                        valueTo = a.getInt(valueToIndex, 0);
-                    }
-                    anim.setIntValues(valueFrom, valueTo);
-                } else {
-                    anim.setIntValues(valueFrom);
-                }
+            arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
+        }
+
+        // If anim is not null, then it is an object animator.
+        if (anim != null) {
+            if (theme != null) {
+                arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+                        R.styleable.PropertyAnimator, 0, 0);
             } else {
-                if (hasTo) {
-                    if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = (int) a.getDimension(valueToIndex, 0f);
-                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                        (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                        valueTo = a.getColor(valueToIndex, 0);
-                    } else {
-                        valueTo = a.getInt(valueToIndex, 0);
-                    }
-                    anim.setIntValues(valueTo);
-                }
+                arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
             }
         }
-
-        anim.setDuration(duration);
-        anim.setStartDelay(startDelay);
-
-        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
-            anim.setRepeatCount(
-                    a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
-        }
-        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
-            anim.setRepeatMode(
-                    a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
-                            ValueAnimator.RESTART));
-        }
-        if (evaluator != null) {
-            anim.setEvaluator(evaluator);
-        }
+        parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator);
 
         final int resID =
-                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+                arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
         if (resID > 0) {
-            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+            anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
         }
-        a.recycle();
+
+        arrayAnimator.recycle();
+        arrayObjectAnimator.recycle();
 
         return anim;
     }
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 8947550..da56a77 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -469,7 +469,10 @@
      */
     public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
             Property<T, Float> yProperty, Path path) {
-        return ofFloat(target, xProperty.getName(), yProperty.getName(), path);
+        Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false);
+        PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]);
+        PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]);
+        return ofPropertyValuesHolder(target, x, y);
     }
 
     /**
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
index bc4843d..810f050 100644
--- a/core/java/android/animation/StateListAnimator.java
+++ b/core/java/android/animation/StateListAnimator.java
@@ -141,7 +141,7 @@
             return;
         }
         if (mLastMatch != null) {
-            cancel(mLastMatch);
+            cancel();
         }
         mLastMatch = match;
         if (match != null) {
@@ -151,13 +151,15 @@
 
     private void start(Tuple match) {
         match.mAnimator.setTarget(getTarget());
-        mRunningAnimator = match.mAnimator;
-        match.mAnimator.start();
+        mRunningAnimator = match.mAnimator.clone();
+        mRunningAnimator.start();
     }
 
-    private void cancel(Tuple lastMatch) {
-        lastMatch.mAnimator.cancel();
-        lastMatch.mAnimator.setTarget(null);
+    private void cancel() {
+        if (mRunningAnimator != null) {
+            mRunningAnimator.cancel();
+            mRunningAnimator = null;
+        }
     }
 
     /**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f6883e2..228d82e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -779,7 +780,7 @@
     private Thread mUiThread;
     final Handler mHandler = new Handler();
 
-    private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+    ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
     SharedElementListener mEnterTransitionListener = SharedElementListener.NULL_LISTENER;
     SharedElementListener mExitTransitionListener = SharedElementListener.NULL_LISTENER;
 
@@ -929,7 +930,8 @@
 
     /**
      * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
-     * the attribute {@link android.R.attr#persistable} set true.
+     * the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>.
      *
      * @param savedInstanceState if the activity is being re-initialized after
      *     previously being shut down then this Bundle contains the data it most
@@ -1012,8 +1014,9 @@
 
     /**
      * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities
-     * created with the attribute {@link android.R.attr#persistable}. The {@link
-     * android.os.PersistableBundle} passed came from the restored PersistableBundle first
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+     * came from the restored PersistableBundle first
      * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
      *
      * <p>This method is called between {@link #onStart} and
@@ -1111,7 +1114,8 @@
 
     /**
      * This is the same as {@link #onPostCreate(Bundle)} but is called for activities
-     * created with the attribute {@link android.R.attr#persistable}.
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>.
      *
      * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState}
      * @param persistentState The data caming from the PersistableBundle first
@@ -1352,10 +1356,10 @@
 
     /**
      * This is the same as {@link #onSaveInstanceState} but is called for activities
-     * created with the attribute {@link android.R.attr#persistable}. The {@link
-     * android.os.PersistableBundle} passed in will be saved and presented in
-     * {@link #onCreate(Bundle, PersistableBundle)} the first time that this activity
-     * is restarted following the next device reboot.
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+     * in will be saved and presented in {@link #onCreate(Bundle, PersistableBundle)}
+     * the first time that this activity is restarted following the next device reboot.
      *
      * @param outState Bundle in which to place your saved state.
      * @param outPersistentState State which will be saved across reboots.
@@ -3662,11 +3666,7 @@
      * @see #startActivity
      */
     public void startActivityForResult(Intent intent, int requestCode) {
-        Bundle options = null;
-        if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
-            options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
-        }
-        startActivityForResult(intent, requestCode, options);
+        startActivityForResult(intent, requestCode, null);
     }
 
     /**
@@ -4217,11 +4217,7 @@
      */
     public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
             int requestCode) {
-        Bundle options = null;
-        if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
-            options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
-        }
-        startActivityFromFragment(fragment, intent, requestCode, options);
+        startActivityFromFragment(fragment, intent, requestCode, null);
     }
 
     /**
@@ -5135,78 +5131,7 @@
             return onCreateView(name, context, attrs);
         }
         
-        String fname = attrs.getAttributeValue(null, "class");
-        TypedArray a = 
-            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
-        if (fname == null) {
-            fname = a.getString(com.android.internal.R.styleable.Fragment_name);
-        }
-        int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
-        String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
-        a.recycle();
-        
-        int containerId = parent != null ? parent.getId() : 0;
-        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
-            throw new IllegalArgumentException(attrs.getPositionDescription()
-                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
-        }
-
-        // If we restored from a previous state, we may already have
-        // instantiated this fragment from the state and should use
-        // that instance instead of making a new one.
-        Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
-        if (fragment == null && tag != null) {
-            fragment = mFragments.findFragmentByTag(tag);
-        }
-        if (fragment == null && containerId != View.NO_ID) {
-            fragment = mFragments.findFragmentById(containerId);
-        }
-
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
-                + Integer.toHexString(id) + " fname=" + fname
-                + " existing=" + fragment);
-        if (fragment == null) {
-            fragment = Fragment.instantiate(this, fname);
-            fragment.mFromLayout = true;
-            fragment.mFragmentId = id != 0 ? id : containerId;
-            fragment.mContainerId = containerId;
-            fragment.mTag = tag;
-            fragment.mInLayout = true;
-            fragment.mFragmentManager = mFragments;
-            fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
-            mFragments.addFragment(fragment, true);
-
-        } else if (fragment.mInLayout) {
-            // A fragment already exists and it is not one we restored from
-            // previous state.
-            throw new IllegalArgumentException(attrs.getPositionDescription()
-                    + ": Duplicate id 0x" + Integer.toHexString(id)
-                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
-                    + " with another fragment for " + fname);
-        } else {
-            // This fragment was retained from a previous instance; get it
-            // going now.
-            fragment.mInLayout = true;
-            // If this fragment is newly instantiated (either right now, or
-            // from last saved state), then give it the attributes to
-            // initialize itself.
-            if (!fragment.mRetaining) {
-                fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
-            }
-            mFragments.moveToState(fragment);
-        }
-
-        if (fragment.mView == null) {
-            throw new IllegalStateException("Fragment " + fname
-                    + " did not create a view.");
-        }
-        if (id != 0) {
-            fragment.mView.setId(id);
-        }
-        if (fragment.mView.getTag() == null) {
-            fragment.mView.setTag(tag);
-        }
-        return fragment.mView;
+        return mFragments.onCreateView(parent, name, context, attrs);
     }
 
     /**
@@ -5320,13 +5245,15 @@
      * drawn and it is safe to make this Activity translucent again.
      * @param options activity options delivered to the activity below this one. The options
      * are retrieved using {@link #getActivityOptions}.
+     * @return <code>true</code> if Window was opaque and will become translucent or
+     * <code>false</code> if window was translucent and no change needed to be made.
      *
      * @see #convertFromTranslucent()
      * @see TranslucentConversionListener
      *
      * @hide
      */
-    public void convertToTranslucent(TranslucentConversionListener callback, 
+    public boolean convertToTranslucent(TranslucentConversionListener callback,
             ActivityOptions options) {
         boolean drawComplete;
         try {
@@ -5343,6 +5270,7 @@
             // Window is already translucent.
             mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
         }
+        return mChangeCanvasToTranslucent;
     }
 
     /** @hide */
@@ -5929,14 +5857,21 @@
     }
 
     /**
-     * Put this Activity in a mode where the user is locked to the
+     * Request to put this Activity in a mode where the user is locked to the
      * current task.
      *
      * This will prevent the user from launching other apps, going to settings,
      * or reaching the home screen.
      *
-     * Lock task mode will only start if the activity has been whitelisted by the
-     * Device Owner through DevicePolicyManager#setLockTaskComponents.
+     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true
+     * for this component then the app will go directly into Lock Task mode.  The user
+     * will not be able to exit this mode until {@link Activity#stopLockTask()} is called.
+     *
+     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
+     * then the system will prompt the user with a dialog requesting permission to enter
+     * this mode.  When entered through this method the user can exit at any time by
+     * swiping down twice from the top of the screen.  Calling stopLockTask will also
+     * exit the mode.
      */
     public void startLockTask() {
         try {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 788ac56..87140a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -538,6 +538,30 @@
         }
 
         /**
+         * Sets the label for this task description.
+         * @hide
+         */
+        public void setLabel(String label) {
+            mLabel = label;
+        }
+
+        /**
+         * Sets the primary color for this task description.
+         * @hide
+         */
+        public void setPrimaryColor(int primaryColor) {
+            mColorPrimary = primaryColor;
+        }
+
+        /**
+         * Sets the icon for this task description.
+         * @hide
+         */
+        public void setIcon(Bitmap icon) {
+            mIcon = icon;
+        }
+
+        /**
          * @return The label and description of the current state of this task.
          */
         public String getLabel() {
@@ -652,6 +676,12 @@
         public int userId;
 
         /**
+         * The last time this task was active.
+         * @hide
+         */
+        public long lastActiveTime;
+
+        /**
          * The recent activity values for the highest activity in the stack to have set the values.
          * {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
          *
@@ -688,6 +718,7 @@
             }
             dest.writeInt(stackId);
             dest.writeInt(userId);
+            dest.writeLong(lastActiveTime);
         }
 
         public void readFromParcel(Parcel source) {
@@ -700,6 +731,7 @@
                     TaskDescription.CREATOR.createFromParcel(source) : null;
             stackId = source.readInt();
             userId = source.readInt();
+            lastActiveTime = source.readLong();
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0f65454..572d389 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;
@@ -2143,6 +2145,13 @@
             return true;
         }
 
+        case START_LOCK_TASK_BY_CURRENT: {
+            data.enforceInterface(IActivityManager.descriptor);
+            startLockTaskModeOnCurrent();
+            reply.writeNoException();
+            return true;
+        }
+
         case STOP_LOCK_TASK_MODE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             stopLockTaskMode();
@@ -2150,6 +2159,13 @@
             return true;
         }
 
+        case STOP_LOCK_TASK_BY_CURRENT: {
+            data.enforceInterface(IActivityManager.descriptor);
+            stopLockTaskModeOnCurrent();
+            reply.writeNoException();
+            return true;
+        }
+
         case IS_IN_LOCK_TASK_MODE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final boolean isInLockTaskMode = isInLockTaskMode();
@@ -3339,7 +3355,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 +3367,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;
@@ -4943,6 +4961,17 @@
     }
 
     @Override
+    public void startLockTaskModeOnCurrent() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(START_LOCK_TASK_BY_CURRENT, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public void stopLockTaskMode() throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -4954,6 +4983,17 @@
     }
 
     @Override
+    public void stopLockTaskModeOnCurrent() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(STOP_LOCK_TASK_BY_CURRENT, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public boolean isInLockTaskMode() throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a057c3e..6b5549d 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -29,8 +29,6 @@
 import android.view.Window;
 
 import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Helper class for building an options Bundle that can be used with
@@ -110,9 +108,9 @@
 
     private static final String KEY_TRANSITION_IS_RETURNING = "android:transitionIsReturning";
     private static final String KEY_TRANSITION_SHARED_ELEMENTS = "android:sharedElementNames";
-    private static final String KEY_LOCAL_SHARED_ELEMENTS = "android:localSharedElementNames";
     private static final String KEY_RESULT_DATA = "android:resultData";
     private static final String KEY_RESULT_CODE = "android:resultCode";
+    private static final String KEY_EXIT_COORDINATOR_INDEX = "android:exitCoordinatorIndex";
 
     /** @hide */
     public static final int ANIM_NONE = 0;
@@ -140,9 +138,9 @@
     private ResultReceiver mTransitionReceiver;
     private boolean mIsReturning;
     private ArrayList<String> mSharedElementNames;
-    private ArrayList<String> mLocalSharedElementNames;
     private Intent mResultData;
     private int mResultCode;
+    private int mExitCoordinatorIndex;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -389,22 +387,31 @@
         opts.mAnimationType = ANIM_SCENE_TRANSITION;
 
         ArrayList<String> names = new ArrayList<String>();
-        ArrayList<String> mappedNames = new ArrayList<String>();
+        ArrayList<View> views = new ArrayList<View>();
 
         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");
+                }
+                names.add(sharedElementName);
+                View view = sharedElement.first;
+                if (view == null) {
+                    throw new IllegalArgumentException("Shared element must not be null");
+                }
+                views.add(sharedElement.first);
             }
         }
 
         ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
-                mappedNames, false);
+                views, false);
         opts.mTransitionReceiver = exit;
         opts.mSharedElementNames = names;
-        opts.mLocalSharedElementNames = mappedNames;
         opts.mIsReturning = false;
+        opts.mExitCoordinatorIndex =
+                activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
         return opts;
     }
 
@@ -419,6 +426,8 @@
         opts.mIsReturning = true;
         opts.mResultCode = resultCode;
         opts.mResultData = resultData;
+        opts.mExitCoordinatorIndex =
+                activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
         return opts;
     }
 
@@ -457,9 +466,9 @@
                 mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
                 mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
                 mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
-                mLocalSharedElementNames = opts.getStringArrayList(KEY_LOCAL_SHARED_ELEMENTS);
                 mResultData = opts.getParcelable(KEY_RESULT_DATA);
                 mResultCode = opts.getInt(KEY_RESULT_CODE);
+                mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
                 break;
         }
     }
@@ -515,18 +524,7 @@
     }
 
     /** @hide */
-    public void dispatchActivityStopped() {
-        if (mTransitionReceiver != null) {
-            mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
-        }
-    }
-
-    /** @hide */
-    public void dispatchStartExit() {
-        if (mTransitionReceiver != null) {
-            mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
-        }
-    }
+    public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
 
     /** @hide */
     public void abort() {
@@ -539,11 +537,6 @@
     }
 
     /** @hide */
-    public void setReturning() {
-        mIsReturning = true;
-    }
-
-    /** @hide */
     public boolean isReturning() {
         return mIsReturning;
     }
@@ -554,9 +547,6 @@
     }
 
     /** @hide */
-    public ArrayList<String> getLocalSharedElementNames() { return mLocalSharedElementNames; }
-
-    /** @hide */
     public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
 
     /** @hide */
@@ -583,10 +573,10 @@
         }
         mTransitionReceiver = null;
         mSharedElementNames = null;
-        mLocalSharedElementNames = null;
         mIsReturning = false;
         mResultData = null;
         mResultCode = 0;
+        mExitCoordinatorIndex = 0;
         switch (otherOptions.mAnimationType) {
             case ANIM_CUSTOM:
                 mAnimationType = otherOptions.mAnimationType;
@@ -633,12 +623,12 @@
                 mAnimationType = otherOptions.mAnimationType;
                 mTransitionReceiver = otherOptions.mTransitionReceiver;
                 mSharedElementNames = otherOptions.mSharedElementNames;
-                mLocalSharedElementNames = otherOptions.mLocalSharedElementNames;
                 mIsReturning = otherOptions.mIsReturning;
                 mThumbnail = null;
                 mAnimationStartedListener = null;
                 mResultData = otherOptions.mResultData;
                 mResultCode = otherOptions.mResultCode;
+                mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
                 break;
         }
     }
@@ -687,9 +677,9 @@
                 }
                 b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
                 b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
-                b.putStringArrayList(KEY_LOCAL_SHARED_ELEMENTS, mLocalSharedElementNames);
                 b.putParcelable(KEY_RESULT_DATA, mResultData);
                 b.putInt(KEY_RESULT_CODE, mResultCode);
+                b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
         return b;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d9adba3..f5514f8 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;
@@ -190,11 +191,13 @@
     /** Reference to singleton {@link ActivityThread} */
     private static ActivityThread sCurrentActivityThread;
     Instrumentation mInstrumentation;
+    String mInstrumentationPackageName = null;
     String mInstrumentationAppDir = null;
-    String mInstrumentationAppLibraryDir = null;
-    String mInstrumentationAppPackage = null;
+    String[] mInstrumentationSplitAppDirs = null;
+    String mInstrumentationLibDir = null;
     String mInstrumentedAppDir = null;
-    String mInstrumentedAppLibraryDir = null;
+    String[] mInstrumentedSplitAppDirs = null;
+    String mInstrumentedLibDir = null;
     boolean mSystemThread = false;
     boolean mJitEnabled = false;
 
@@ -316,7 +319,7 @@
         }
 
         public boolean isPersistable() {
-            return (activityInfo.flags & ActivityInfo.FLAG_PERSISTABLE) != 0;
+            return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
         }
 
         public String toString() {
@@ -1584,11 +1587,11 @@
     /**
      * Creates the top level resources for the given package.
      */
-    Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs,
-            int displayId, Configuration overrideConfiguration,
+    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
+            String[] libDirs, int displayId, Configuration overrideConfiguration,
             LoadedApk pkgInfo) {
-        return mResourcesManager.getTopLevelResources(resDir, overlayDirs, libDirs, displayId,
-                overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
+        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
+                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
     }
 
     final Handler getHandler() {
@@ -4314,16 +4317,20 @@
                     + data.instrumentationName);
             }
 
+            mInstrumentationPackageName = ii.packageName;
             mInstrumentationAppDir = ii.sourceDir;
-            mInstrumentationAppLibraryDir = ii.nativeLibraryDir;
-            mInstrumentationAppPackage = ii.packageName;
+            mInstrumentationSplitAppDirs = ii.splitSourceDirs;
+            mInstrumentationLibDir = ii.nativeLibraryDir;
             mInstrumentedAppDir = data.info.getAppDir();
-            mInstrumentedAppLibraryDir = data.info.getLibDir();
+            mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
+            mInstrumentedLibDir = data.info.getLibDir();
 
             ApplicationInfo instrApp = new ApplicationInfo();
             instrApp.packageName = ii.packageName;
             instrApp.sourceDir = ii.sourceDir;
             instrApp.publicSourceDir = ii.publicSourceDir;
+            instrApp.splitSourceDirs = ii.splitSourceDirs;
+            instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
             instrApp.dataDir = ii.dataDir;
             instrApp.nativeLibraryDir = ii.nativeLibraryDir;
             LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
@@ -5049,6 +5056,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..79dc6a8 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();
 
     /**
@@ -154,12 +151,6 @@
     public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
 
     /**
-     * Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped
-     * to leave the Activity in a good state after it has been hidden.
-     */
-    public static final int MSG_ACTIVITY_STOPPED = 102;
-
-    /**
      * Sent by the exiting coordinator (either EnterTransitionCoordinator
      * or ExitTransitionCoordinator) after the shared elements have
      * become stationary (shared element transition completes). This tells
@@ -200,7 +191,7 @@
      */
     public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108;
 
-    final private Window mWindow;
+    private Window mWindow;
     final protected ArrayList<String> mAllSharedElementNames;
     final protected ArrayList<View> mSharedElements = new ArrayList<View>();
     final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
@@ -209,14 +200,8 @@
     protected ResultReceiver mResultReceiver;
     final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
     final protected boolean mIsReturning;
-
-    public ActivityTransitionCoordinator(Window window,
-            ArrayList<String> allSharedElementNames,
-            ArrayList<String> accepted, ArrayList<String> localNames,
-            SharedElementListener listener, boolean isReturning) {
-        this(window, allSharedElementNames, listener, isReturning);
-        viewsReady(accepted, localNames);
-    }
+    private Runnable mPendingTransition;
+    private boolean mIsStartingTransition;
 
     public ActivityTransitionCoordinator(Window window,
             ArrayList<String> allSharedElementNames,
@@ -228,8 +213,8 @@
         mIsReturning = isReturning;
     }
 
-    protected void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
-        setSharedElements(accepted, localNames);
+    protected void viewsReady(ArrayMap<String, View> sharedElements) {
+        setSharedElements(sharedElements);
         if (getViewsTransition() != null) {
             getDecor().captureTransitioningViews(mTransitioningViews);
             mTransitioningViews.removeAll(mSharedElements);
@@ -237,6 +222,16 @@
         setEpicenter();
     }
 
+    protected void stripOffscreenViews() {
+        Rect r = new Rect();
+        for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+            View view = mTransitioningViews.get(i);
+            if (!view.getGlobalVisibleRect(r)) {
+                mTransitioningViews.remove(i);
+            }
+        }
+    }
+
     protected Window getWindow() {
         return mWindow;
     }
@@ -261,13 +256,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);
         }
     }
@@ -279,11 +269,15 @@
     public ArrayList<String> getMappedNames() {
         ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
         for (int i = 0; i < mSharedElements.size(); i++) {
-            names.add(mSharedElements.get(i).getViewName());
+            names.add(mSharedElements.get(i).getTransitionName());
         }
         return names;
     }
 
+    public ArrayList<View> getMappedViews() {
+        return mSharedElements;
+    }
+
     public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
 
     public static void setViewVisibility(Collection<View> views, int visibility) {
@@ -298,13 +292,17 @@
         if (transition == null || views == null || views.isEmpty()) {
             return null;
         }
+        // Add the targets to a set containing transition so that transition
+        // remains unaffected. We don't want to modify the targets of transition itself.
         TransitionSet set = new TransitionSet();
-        set.addTransition(transition);
         if (views != null) {
-            for (View view: views) {
+            for (View view : views) {
                 set.addTarget(view);
             }
         }
+        // By adding the transition after addTarget, we prevent addTarget from
+        // affecting transition.
+        set.addTransition(transition);
         return set;
     }
 
@@ -329,32 +327,31 @@
         }
     }
 
-    private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) {
+    protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
+            ArrayList<View> localViews) {
+        ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
         if (!mAllSharedElementNames.isEmpty()) {
-            ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
-            getDecor().findNamedViews(sharedElements);
             if (accepted != null) {
-                for (int i = 0; i < localNames.size(); i++) {
-                    String localName = localNames.get(i);
-                    String acceptedName = accepted.get(i);
-                    if (!localName.equals(acceptedName)) {
-                        View view = sharedElements.remove(localName);
-                        if (view != null) {
-                            sharedElements.put(acceptedName, view);
-                        }
-                    }
+                for (int i = 0; i < accepted.size(); i++) {
+                    sharedElements.put(accepted.get(i), localViews.get(i));
                 }
+            } else {
+                getDecor().findNamedViews(sharedElements);
             }
-            sharedElements.retainAll(mAllSharedElementNames);
-            mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
-            sharedElements.retainAll(mAllSharedElementNames);
-            for (int i = 0; i < mAllSharedElementNames.size(); i++) {
-                String name = mAllSharedElementNames.get(i);
-                View sharedElement = sharedElements.get(name);
-                if (sharedElement != null) {
-                    mSharedElementNames.add(name);
-                    mSharedElements.add(sharedElement);
-                }
+        }
+        return sharedElements;
+    }
+
+    private void setSharedElements(ArrayMap<String, View> sharedElements) {
+        sharedElements.retainAll(mAllSharedElementNames);
+        mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
+        sharedElements.retainAll(mAllSharedElementNames);
+        for (int i = 0; i < mAllSharedElementNames.size(); i++) {
+            String name = mAllSharedElementNames.get(i);
+            View sharedElement = sharedElements.get(name);
+            if (sharedElement != null) {
+                mSharedElementNames.add(name);
+                mSharedElements.add(sharedElement);
             }
         }
     }
@@ -484,7 +481,7 @@
                 if (bitmap != null) {
                     snapshot.setBackground(new BitmapDrawable(resources, bitmap));
                 }
-                snapshot.setViewName(name);
+                snapshot.setTransitionName(name);
                 setSharedElementState(snapshot, name, state, parentLoc);
                 snapshots.add(snapshot);
             }
@@ -504,15 +501,30 @@
 
     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 void clearState() {
+        // Clear the state so that we can't hold any references accidentally and leak memory.
+        mWindow = null;
+        mAllSharedElementNames.clear();
+        mSharedElements.clear();
+        mSharedElementNames.clear();
+        mTransitioningViews.clear();
+        mResultReceiver = null;
+        mPendingTransition = null;
+    }
+
+    protected long getFadeDuration() {
+        return getWindow().getTransitionBackgroundFadeDuration();
+    }
+
     /**
      * Captures placement information for Views with a shared element name for
      * Activity Transitions.
@@ -521,20 +533,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) {
+    protected static void captureSharedElementState(View view, String name, Bundle transitionArgs,
+            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());
@@ -560,6 +571,32 @@
         transitionArgs.putBundle(name, sharedElementBundle);
     }
 
+
+    protected void startTransition(Runnable runnable) {
+        if (mIsStartingTransition) {
+            mPendingTransition = runnable;
+        } else {
+            mIsStartingTransition = true;
+            runnable.run();
+        }
+    }
+
+    protected void transitionStarted() {
+        mIsStartingTransition = false;
+    }
+
+    protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter {
+        @Override
+        public void onTransitionStart(Transition transition) {
+            mIsStartingTransition = false;
+            Runnable pending = mPendingTransition;
+            mPendingTransition = null;
+            if (pending != null) {
+                startTransition(pending);
+            }
+        }
+    }
+
     private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
         for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
             if (scaleType == SCALE_TYPE_VALUES[i]) {
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index d94dadd..0304246 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -18,9 +18,11 @@
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.Window;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -31,10 +33,6 @@
 
     private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
 
-    private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom";
-
-    private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo";
-
     private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
 
     private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
@@ -46,16 +44,6 @@
     private ArrayList<String> mEnteringNames;
 
     /**
-     * The shared elements that this Activity as accepted and mapped to local Views.
-     */
-    private ArrayList<String> mEnteringFrom;
-
-    /**
-     * The names of local Views that are mapped to those elements in mEnteringFrom.
-     */
-    private ArrayList<String> mEnteringTo;
-
-    /**
      * The names of shared elements that were shared to the called Activity.
      */
     private ArrayList<String> mExitingFrom;
@@ -66,10 +54,15 @@
     private ArrayList<String> mExitingTo;
 
     /**
-     * The ActivityOptions used to call an Activity. Used to make the elements restore
+     * The local Views that were shared out, mapped to those elements in mExitingFrom.
+     */
+    private ArrayList<View> mExitingToView;
+
+    /**
+     * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
      * Visibility of exited Views.
      */
-    private ActivityOptions mCalledActivityOptions;
+    private ExitTransitionCoordinator mCalledExitCoordinator;
 
     /**
      * We must be able to cancel entering transitions to stop changing the Window to
@@ -92,15 +85,42 @@
      */
     private boolean mIsEnterPostponed;
 
+    /**
+     * Potential exit transition coordinators.
+     */
+    private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
+
+    /**
+     * Next key for mExitTransitionCoordinator.
+     */
+    private int mExitTransitionCoordinatorsKey = 1;
+
     public ActivityTransitionState() {
     }
 
+    public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
+        if (mExitTransitionCoordinators == null) {
+            mExitTransitionCoordinators =
+                    new SparseArray<WeakReference<ExitTransitionCoordinator>>();
+        }
+        WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
+        // clean up old references:
+        for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
+            WeakReference<ExitTransitionCoordinator> oldRef
+                    = mExitTransitionCoordinators.valueAt(i);
+            if (oldRef.get() == null) {
+                mExitTransitionCoordinators.removeAt(i);
+            }
+        }
+        int newKey = mExitTransitionCoordinatorsKey++;
+        mExitTransitionCoordinators.append(newKey, ref);
+        return newKey;
+    }
+
     public void readState(Bundle bundle) {
         if (bundle != null) {
             if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
                 mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
-                mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM);
-                mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO);
             }
             if (mEnterTransitionCoordinator == null) {
                 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
@@ -112,8 +132,6 @@
     public void saveState(Bundle bundle) {
         if (mEnteringNames != null) {
             bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
-            bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom);
-            bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo);
         }
         if (mExitingFrom != null) {
             bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
@@ -169,16 +187,19 @@
 
     private void startEnter() {
         if (mEnterActivityOptions.isReturning()) {
-            mEnterTransitionCoordinator.viewsReady(mExitingFrom, mExitingTo);
+            if (mExitingToView != null) {
+                mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingToView);
+            } else {
+                mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
+            }
         } else {
-            mEnterTransitionCoordinator.viewsReady(null, null);
+            mEnterTransitionCoordinator.namedViewsReady(null, null);
             mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
-            mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
-            mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
         }
 
         mExitingFrom = null;
         mExitingTo = null;
+        mExitingToView = null;
         mEnterActivityOptions = null;
     }
 
@@ -194,10 +215,21 @@
         restoreExitedViews();
     }
 
+    public void clear() {
+        mEnteringNames = null;
+        mExitingFrom = null;
+        mExitingTo = null;
+        mExitingToView = null;
+        mCalledExitCoordinator = null;
+        mEnterTransitionCoordinator = null;
+        mEnterActivityOptions = null;
+        mExitTransitionCoordinators = null;
+    }
+
     private void restoreExitedViews() {
-        if (mCalledActivityOptions != null) {
-            mCalledActivityOptions.dispatchActivityStopped();
-            mCalledActivityOptions = null;
+        if (mCalledExitCoordinator != null) {
+            mCalledExitCoordinator.resetViews();
+            mCalledExitCoordinator = null;
         }
     }
 
@@ -215,8 +247,7 @@
                 activity.getWindow().getDecorView().findNamedViews(sharedElements);
 
                 ExitTransitionCoordinator exitCoordinator =
-                        new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom,
-                                mEnteringTo, true);
+                        new ExitTransitionCoordinator(activity, mEnteringNames, null, null, true);
                 exitCoordinator.startExit(activity.mResultCode, activity.mResultData);
             }
             return true;
@@ -227,11 +258,20 @@
         if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
             return;
         }
-        mCalledActivityOptions = new ActivityOptions(options);
-        if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
-            mExitingFrom = mCalledActivityOptions.getSharedElementNames();
-            mExitingTo = mCalledActivityOptions.getLocalSharedElementNames();
-            mCalledActivityOptions.dispatchStartExit();
+        ActivityOptions activityOptions = new ActivityOptions(options);
+        if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+            int key = activityOptions.getExitCoordinatorKey();
+            int index = mExitTransitionCoordinators.indexOfKey(key);
+            if (index >= 0) {
+                mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
+                mExitTransitionCoordinators.removeAt(index);
+                if (mCalledExitCoordinator != null) {
+                    mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
+                    mExitingTo = mCalledExitCoordinator.getMappedNames();
+                    mExitingToView = mCalledExitCoordinator.getMappedViews();
+                    mCalledExitCoordinator.startExit();
+                }
+            }
         }
     }
 }
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5867232..a480219 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -189,7 +189,9 @@
     /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
     public static final int OP_GET_USAGE_STATS = 43;
     /** @hide */
-    public static final int _NUM_OP = 44;
+    public static final int OP_MUTE_MICROPHONE = 44;
+    /** @hide */
+    public static final int _NUM_OP = 45;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION =
@@ -257,6 +259,7 @@
             OP_COARSE_LOCATION,
             OP_COARSE_LOCATION,
             OP_GET_USAGE_STATS,
+            OP_MUTE_MICROPHONE
     };
 
     /**
@@ -308,6 +311,7 @@
             OPSTR_MONITOR_LOCATION,
             OPSTR_MONITOR_HIGH_POWER_LOCATION,
             null,
+            null,
     };
 
     /**
@@ -358,7 +362,8 @@
             "WAKE_LOCK",
             "MONITOR_LOCATION",
             "MONITOR_HIGH_POWER_LOCATION",
-            "GET_USAGE_STATS"
+            "GET_USAGE_STATS",
+            "OP_MUTE_MICROPHONE",
     };
 
     /**
@@ -410,6 +415,7 @@
             null, // no permission for generic location monitoring
             null, // no permission for high power location monitoring
             android.Manifest.permission.PACKAGE_USAGE_STATS,
+            null, // no permission for muting/unmuting microphone
     };
 
     /**
@@ -462,6 +468,7 @@
             null, //MONITOR_LOCATION
             null, //MONITOR_HIGH_POWER_LOCATION
             null, //GET_USAGE_STATS
+            UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
     };
 
     /**
@@ -512,6 +519,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_IGNORED, // OP_GET_USAGE_STATS
+            AppOpsManager.MODE_ALLOWED,
     };
 
     /**
@@ -566,6 +574,7 @@
             false,
             false,
             false,
+            false,
     };
 
     private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2f35160..cb48e58 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -48,11 +48,13 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Display;
@@ -619,10 +621,15 @@
     }
 
     @Override
-    public ProviderInfo resolveContentProvider(String name,
-                                               int flags) {
+    public ProviderInfo resolveContentProvider(String name, int flags) {
+        return resolveContentProviderAsUser(name, flags, mContext.getUserId());
+    }
+
+    /** @hide **/
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
         try {
-            return mPM.resolveContentProvider(name, flags, mContext.getUserId());
+            return mPM.resolveContentProvider(name, flags, userId);
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -823,8 +830,10 @@
         if (app.packageName.equals("system")) {
             return mContext.mMainThread.getSystemContext().getResources();
         }
+        final boolean sameUid = (app.uid == Process.myUid());
         Resources r = mContext.mMainThread.getTopLevelResources(
-                app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
+                sameUid ? app.sourceDir : app.publicSourceDir,
+                sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
                 app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
         if (r != null) {
             return r;
@@ -1455,10 +1464,22 @@
      * @hide
      */
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId,
+            int flags) {
         try {
-            mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest);
+            mPM.addCrossProfileIntentFilter(filter, sourceUserId, targetUserId, flags);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void addCrossProfileIntentsForPackage(String packageName,
+            int sourceUserId, int targetUserId) {
+        try {
+            mPM.addCrossProfileIntentsForPackage(packageName, sourceUserId, targetUserId);
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1468,14 +1489,23 @@
      * @hide
      */
     @Override
-    public void clearForwardingIntentFilters(int userIdOrig) {
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
         try {
-            mPM.clearForwardingIntentFilters(userIdOrig);
+            mPM.clearCrossProfileIntentFilters(sourceUserId);
         } catch (RemoteException e) {
             // Should never happen!
         }
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public Bitmap getUserIcon(int userId) {
+        UserManager um = UserManager.get(mContext);
+        return um.getUserIcon(userId);
+    }
+
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index ef4099f..5998d7a 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -1184,6 +1184,7 @@
         data.writeInt(level);
         mRemote.transact(SCHEDULE_TRIM_MEMORY_TRANSACTION, data, null,
                 IBinder.FLAG_ONEWAY);
+        data.recycle();
     }
 
     public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin,
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 89ee145..4433a3a 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -16,36 +16,53 @@
 
 package android.app;
 
+import com.android.internal.util.FastPrintWriter;
+
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LogWriter;
-import com.android.internal.util.FastPrintWriter;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 
 final class BackStackState implements Parcelable {
     final int[] mOps;
     final int mTransition;
     final int mTransitionStyle;
+    final int mCustomTransition;
+    final int mSceneRoot;
     final String mName;
     final int mIndex;
     final int mBreadCrumbTitleRes;
     final CharSequence mBreadCrumbTitleText;
     final int mBreadCrumbShortTitleRes;
     final CharSequence mBreadCrumbShortTitleText;
+    final ArrayList<String> mSharedElementSourceNames;
+    final ArrayList<String> mSharedElementTargetNames;
 
     public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
         int numRemoved = 0;
         BackStackRecord.Op op = bse.mHead;
         while (op != null) {
-            if (op.removed != null) numRemoved += op.removed.size();
+            if (op.removed != null) {
+                numRemoved += op.removed.size();
+            }
             op = op.next;
         }
-        mOps = new int[bse.mNumOp*7 + numRemoved];
+        mOps = new int[bse.mNumOp * 7 + numRemoved];
 
         if (!bse.mAddToBackStack) {
             throw new IllegalStateException("Not on back stack");
@@ -63,7 +80,7 @@
             if (op.removed != null) {
                 final int N = op.removed.size();
                 mOps[pos++] = N;
-                for (int i=0; i<N; i++) {
+                for (int i = 0; i < N; i++) {
                     mOps[pos++] = op.removed.get(i).mIndex;
                 }
             } else {
@@ -79,6 +96,10 @@
         mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
         mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
         mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+        mCustomTransition = bse.mCustomTransition;
+        mSceneRoot = bse.mSceneRoot;
+        mSharedElementSourceNames = bse.mSharedElementSourceNames;
+        mSharedElementTargetNames = bse.mSharedElementTargetNames;
     }
 
     public BackStackState(Parcel in) {
@@ -91,6 +112,10 @@
         mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mBreadCrumbShortTitleRes = in.readInt();
         mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mCustomTransition = in.readInt();
+        mSceneRoot = in.readInt();
+        mSharedElementSourceNames = in.createStringArrayList();
+        mSharedElementTargetNames = in.createStringArrayList();
     }
 
     public BackStackRecord instantiate(FragmentManagerImpl fm) {
@@ -100,8 +125,10 @@
         while (pos < mOps.length) {
             BackStackRecord.Op op = new BackStackRecord.Op();
             op.cmd = mOps[pos++];
-            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                    "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+            if (FragmentManagerImpl.DEBUG) {
+                Log.v(FragmentManagerImpl.TAG,
+                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+            }
             int findex = mOps[pos++];
             if (findex >= 0) {
                 Fragment f = fm.mActive.get(findex);
@@ -116,9 +143,11 @@
             final int N = mOps[pos++];
             if (N > 0) {
                 op.removed = new ArrayList<Fragment>(N);
-                for (int i=0; i<N; i++) {
-                    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                            "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
+                for (int i = 0; i < N; i++) {
+                    if (FragmentManagerImpl.DEBUG) {
+                        Log.v(FragmentManagerImpl.TAG,
+                                "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
+                    }
                     Fragment r = fm.mActive.get(mOps[pos++]);
                     op.removed.add(r);
                 }
@@ -135,6 +164,10 @@
         bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
         bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
         bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+        bse.mCustomTransition = mCustomTransition;
+        bse.mSceneRoot = mSceneRoot;
+        bse.mSharedElementSourceNames = mSharedElementSourceNames;
+        bse.mSharedElementTargetNames = mSharedElementTargetNames;
         bse.bumpBackStackNesting(1);
         return bse;
     }
@@ -153,6 +186,10 @@
         TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
         dest.writeInt(mBreadCrumbShortTitleRes);
         TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+        dest.writeInt(mCustomTransition);
+        dest.writeInt(mSceneRoot);
+        dest.writeStringList(mSharedElementSourceNames);
+        dest.writeStringList(mSharedElementTargetNames);
     }
 
     public static final Parcelable.Creator<BackStackState> CREATOR
@@ -217,6 +254,11 @@
     int mBreadCrumbShortTitleRes;
     CharSequence mBreadCrumbShortTitleText;
 
+    int mCustomTransition;
+    int mSceneRoot;
+    ArrayList<String> mSharedElementSourceNames;
+    ArrayList<String> mSharedElementTargetNames;
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
@@ -240,78 +282,112 @@
 
     void dump(String prefix, PrintWriter writer, boolean full) {
         if (full) {
-            writer.print(prefix); writer.print("mName="); writer.print(mName);
-                    writer.print(" mIndex="); writer.print(mIndex);
-                    writer.print(" mCommitted="); writer.println(mCommitted);
+            writer.print(prefix);
+            writer.print("mName=");
+            writer.print(mName);
+            writer.print(" mIndex=");
+            writer.print(mIndex);
+            writer.print(" mCommitted=");
+            writer.println(mCommitted);
             if (mTransition != FragmentTransaction.TRANSIT_NONE) {
-                writer.print(prefix); writer.print("mTransition=#");
-                        writer.print(Integer.toHexString(mTransition));
-                        writer.print(" mTransitionStyle=#");
-                        writer.println(Integer.toHexString(mTransitionStyle));
+                writer.print(prefix);
+                writer.print("mTransition=#");
+                writer.print(Integer.toHexString(mTransition));
+                writer.print(" mTransitionStyle=#");
+                writer.println(Integer.toHexString(mTransitionStyle));
             }
-            if (mEnterAnim != 0 || mExitAnim !=0) {
-                writer.print(prefix); writer.print("mEnterAnim=#");
-                        writer.print(Integer.toHexString(mEnterAnim));
-                        writer.print(" mExitAnim=#");
-                        writer.println(Integer.toHexString(mExitAnim));
+            if (mEnterAnim != 0 || mExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mEnterAnim=#");
+                writer.print(Integer.toHexString(mEnterAnim));
+                writer.print(" mExitAnim=#");
+                writer.println(Integer.toHexString(mExitAnim));
             }
-            if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
-                writer.print(prefix); writer.print("mPopEnterAnim=#");
-                        writer.print(Integer.toHexString(mPopEnterAnim));
-                        writer.print(" mPopExitAnim=#");
-                        writer.println(Integer.toHexString(mPopExitAnim));
+            if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mPopEnterAnim=#");
+                writer.print(Integer.toHexString(mPopEnterAnim));
+                writer.print(" mPopExitAnim=#");
+                writer.println(Integer.toHexString(mPopExitAnim));
             }
             if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
-                writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
-                        writer.print(Integer.toHexString(mBreadCrumbTitleRes));
-                        writer.print(" mBreadCrumbTitleText=");
-                        writer.println(mBreadCrumbTitleText);
+                writer.print(prefix);
+                writer.print("mBreadCrumbTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+                writer.print(" mBreadCrumbTitleText=");
+                writer.println(mBreadCrumbTitleText);
             }
             if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
-                writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
-                        writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
-                        writer.print(" mBreadCrumbShortTitleText=");
-                        writer.println(mBreadCrumbShortTitleText);
+                writer.print(prefix);
+                writer.print("mBreadCrumbShortTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+                writer.print(" mBreadCrumbShortTitleText=");
+                writer.println(mBreadCrumbShortTitleText);
             }
         }
 
         if (mHead != null) {
-            writer.print(prefix); writer.println("Operations:");
+            writer.print(prefix);
+            writer.println("Operations:");
             String innerPrefix = prefix + "    ";
             Op op = mHead;
             int num = 0;
             while (op != null) {
                 String cmdStr;
                 switch (op.cmd) {
-                    case OP_NULL: cmdStr="NULL"; break;
-                    case OP_ADD: cmdStr="ADD"; break;
-                    case OP_REPLACE: cmdStr="REPLACE"; break;
-                    case OP_REMOVE: cmdStr="REMOVE"; break;
-                    case OP_HIDE: cmdStr="HIDE"; break;
-                    case OP_SHOW: cmdStr="SHOW"; break;
-                    case OP_DETACH: cmdStr="DETACH"; break;
-                    case OP_ATTACH: cmdStr="ATTACH"; break;
-                    default: cmdStr="cmd=" + op.cmd; break;
+                    case OP_NULL:
+                        cmdStr = "NULL";
+                        break;
+                    case OP_ADD:
+                        cmdStr = "ADD";
+                        break;
+                    case OP_REPLACE:
+                        cmdStr = "REPLACE";
+                        break;
+                    case OP_REMOVE:
+                        cmdStr = "REMOVE";
+                        break;
+                    case OP_HIDE:
+                        cmdStr = "HIDE";
+                        break;
+                    case OP_SHOW:
+                        cmdStr = "SHOW";
+                        break;
+                    case OP_DETACH:
+                        cmdStr = "DETACH";
+                        break;
+                    case OP_ATTACH:
+                        cmdStr = "ATTACH";
+                        break;
+                    default:
+                        cmdStr = "cmd=" + op.cmd;
+                        break;
                 }
-                writer.print(prefix); writer.print("  Op #"); writer.print(num);
-                        writer.print(": "); writer.print(cmdStr);
-                        writer.print(" "); writer.println(op.fragment);
+                writer.print(prefix);
+                writer.print("  Op #");
+                writer.print(num);
+                writer.print(": ");
+                writer.print(cmdStr);
+                writer.print(" ");
+                writer.println(op.fragment);
                 if (full) {
                     if (op.enterAnim != 0 || op.exitAnim != 0) {
-                        writer.print(innerPrefix); writer.print("enterAnim=#");
-                                writer.print(Integer.toHexString(op.enterAnim));
-                                writer.print(" exitAnim=#");
-                                writer.println(Integer.toHexString(op.exitAnim));
+                        writer.print(innerPrefix);
+                        writer.print("enterAnim=#");
+                        writer.print(Integer.toHexString(op.enterAnim));
+                        writer.print(" exitAnim=#");
+                        writer.println(Integer.toHexString(op.exitAnim));
                     }
                     if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
-                        writer.print(innerPrefix); writer.print("popEnterAnim=#");
-                                writer.print(Integer.toHexString(op.popEnterAnim));
-                                writer.print(" popExitAnim=#");
-                                writer.println(Integer.toHexString(op.popExitAnim));
+                        writer.print(innerPrefix);
+                        writer.print("popEnterAnim=#");
+                        writer.print(Integer.toHexString(op.popEnterAnim));
+                        writer.print(" popExitAnim=#");
+                        writer.println(Integer.toHexString(op.popExitAnim));
                     }
                 }
                 if (op.removed != null && op.removed.size() > 0) {
-                    for (int i=0; i<op.removed.size(); i++) {
+                    for (int i = 0; i < op.removed.size(); i++) {
                         writer.print(innerPrefix);
                         if (op.removed.size() == 1) {
                             writer.print("Removed: ");
@@ -319,8 +395,10 @@
                             if (i == 0) {
                                 writer.println("Removed:");
                             }
-                            writer.print(innerPrefix); writer.print("  #"); writer.print(i);
-                                    writer.print(": "); 
+                            writer.print(innerPrefix);
+                            writer.print("  #");
+                            writer.print(i);
+                            writer.print(": ");
                         }
                         writer.println(op.removed.get(i));
                     }
@@ -494,6 +572,51 @@
         return this;
     }
 
+    @Override
+    public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
+        mSceneRoot = sceneRootId;
+        mCustomTransition = transitionId;
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElement(View sharedElement, String name) {
+        String transitionName = sharedElement.getTransitionName();
+        if (transitionName == null) {
+            throw new IllegalArgumentException("Unique transitionNames are required for all" +
+                    " sharedElements");
+        }
+        mSharedElementSourceNames = new ArrayList<String>(1);
+        mSharedElementSourceNames.add(transitionName);
+
+        mSharedElementTargetNames = new ArrayList<String>(1);
+        mSharedElementTargetNames.add(name);
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) {
+        if (sharedElements == null || sharedElements.length == 0) {
+            mSharedElementSourceNames = null;
+            mSharedElementTargetNames = null;
+        } else {
+            ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length);
+            ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length);
+            for (int i = 0; i < sharedElements.length; i++) {
+                String transitionName = sharedElements[i].first.getTransitionName();
+                if (transitionName == null) {
+                    throw new IllegalArgumentException("Unique transitionNames are required for all"
+                            + " sharedElements");
+                }
+                sourceNames.add(transitionName);
+                targetNames.add(sharedElements[i].second);
+            }
+            mSharedElementSourceNames = sourceNames;
+            mSharedElementTargetNames = targetNames;
+        }
+        return this;
+    }
+
     public FragmentTransaction setTransitionStyle(int styleRes) {
         mTransitionStyle = styleRes;
         return this;
@@ -550,21 +673,27 @@
         if (!mAddToBackStack) {
             return;
         }
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
-                + " by " + amt);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Bump nesting in " + this
+                    + " by " + amt);
+        }
         Op op = mHead;
         while (op != null) {
             if (op.fragment != null) {
                 op.fragment.mBackStackNesting += amt;
-                if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                        + op.fragment + " to " + op.fragment.mBackStackNesting);
+                if (FragmentManagerImpl.DEBUG) {
+                    Log.v(TAG, "Bump nesting of "
+                            + op.fragment + " to " + op.fragment.mBackStackNesting);
+                }
             }
             if (op.removed != null) {
-                for (int i=op.removed.size()-1; i>=0; i--) {
+                for (int i = op.removed.size() - 1; i >= 0; i--) {
                     Fragment r = op.removed.get(i);
                     r.mBackStackNesting += amt;
-                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                            + r + " to " + r.mBackStackNesting);
+                    if (FragmentManagerImpl.DEBUG) {
+                        Log.v(TAG, "Bump nesting of "
+                                + r + " to " + r.mBackStackNesting);
+                    }
                 }
             }
             op = op.next;
@@ -578,9 +707,11 @@
     public int commitAllowingStateLoss() {
         return commitInternal(true);
     }
-    
+
     int commitInternal(boolean allowStateLoss) {
-        if (mCommitted) throw new IllegalStateException("commit already called");
+        if (mCommitted) {
+            throw new IllegalStateException("commit already called");
+        }
         if (FragmentManagerImpl.DEBUG) {
             Log.v(TAG, "Commit: " + this);
             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -597,9 +728,11 @@
         mManager.enqueueAction(this, allowStateLoss);
         return mIndex;
     }
-    
+
     public void run() {
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Run: " + this);
+        }
 
         if (mAddToBackStack) {
             if (mIndex < 0) {
@@ -609,6 +742,9 @@
 
         bumpBackStackNesting(1);
 
+        TransitionState state = beginTransition(mSharedElementSourceNames,
+                mSharedElementTargetNames);
+
         Op op = mHead;
         while (op != null) {
             switch (op.cmd) {
@@ -616,14 +752,17 @@
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.addFragment(f, false);
-                } break;
+                }
+                break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
                     if (mManager.mAdded != null) {
-                        for (int i=0; i<mManager.mAdded.size(); i++) {
+                        for (int i = 0; i < mManager.mAdded.size(); i++) {
                             Fragment old = mManager.mAdded.get(i);
-                            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
-                                    "OP_REPLACE: adding=" + f + " old=" + old);
+                            if (FragmentManagerImpl.DEBUG) {
+                                Log.v(TAG,
+                                        "OP_REPLACE: adding=" + f + " old=" + old);
+                            }
                             if (f == null || old.mContainerId == f.mContainerId) {
                                 if (old == f) {
                                     op.fragment = f = null;
@@ -635,8 +774,10 @@
                                     old.mNextAnim = op.exitAnim;
                                     if (mAddToBackStack) {
                                         old.mBackStackNesting += 1;
-                                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                                                + old + " to " + old.mBackStackNesting);
+                                        if (FragmentManagerImpl.DEBUG) {
+                                            Log.v(TAG, "Bump nesting of "
+                                                    + old + " to " + old.mBackStackNesting);
+                                        }
                                     }
                                     mManager.removeFragment(old, mTransition, mTransitionStyle);
                                 }
@@ -647,32 +788,38 @@
                         f.mNextAnim = op.enterAnim;
                         mManager.addFragment(f, false);
                     }
-                } break;
+                }
+                break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.removeFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.hideFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.showFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.detachFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.attachFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
@@ -687,9 +834,174 @@
         if (mAddToBackStack) {
             mManager.addBackStackState(this);
         }
+
+        if (state != null) {
+            updateTransitionEndState(state, mSharedElementTargetNames);
+        }
     }
 
-    public void popFromBackStack(boolean doStateMove) {
+    private TransitionState beginTransition(ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (mCustomTransition <= 0 || mSceneRoot <= 0) {
+            return null;
+        }
+        View rootView = mManager.mContainer.findViewById(mSceneRoot);
+        if (!(rootView instanceof ViewGroup)) {
+            throw new IllegalArgumentException("SceneRoot is not a ViewGroup");
+        }
+        TransitionState state = new TransitionState();
+        // get Transition scene root and create Transitions
+        state.sceneRoot = (ViewGroup) rootView;
+        state.sceneRoot.captureTransitioningViews(state.transitioningViews);
+
+        state.exitTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        state.sharedElementTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        state.enterTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        // Adding a non-existent target view makes sure that the transitions don't target
+        // any views by default. They'll only target the views we tell add. If we don't
+        // add any, then no views will be targeted.
+        View nonExistentView = new View(mManager.mActivity);
+        state.enterTransition.addTarget(nonExistentView);
+        state.exitTransition.addTarget(nonExistentView);
+        state.sharedElementTransition.addTarget(nonExistentView);
+
+        setSharedElementEpicenter(state.enterTransition, state);
+
+        state.excludingTransition = new TransitionSet()
+                .addTransition(state.exitTransition)
+                .addTransition(state.enterTransition);
+
+        if (sourceNames != null) {
+            // Map shared elements.
+            state.sceneRoot.findNamedViews(state.namedViews);
+            state.namedViews.retainAll(sourceNames);
+            View epicenterView = state.namedViews.get(sourceNames.get(0));
+            if (epicenterView != null) {
+                // The epicenter is only the first shared element.
+                setEpicenter(state.exitTransition, epicenterView);
+                setEpicenter(state.sharedElementTransition, epicenterView);
+            }
+            state.transitioningViews.removeAll(state.namedViews.values());
+            state.excludingTransition.addTransition(state.sharedElementTransition);
+            addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+        }
+
+        // Adds the (maybe) exiting views, not including the shared element.
+        // If some stay, that's ok.
+        addTransitioningViews(state.exitTransition, state.transitioningViews);
+
+        // Prepare for shared element name mapping. This could be chained in the case
+        // of popping several back stack states.
+        state.excludingTransition.setNameOverrides(new ArrayMap<String, String>());
+        setNameOverrides(state, sourceNames, targetNames);
+
+        // Don't include any subtree in the views that are hidden when capturing the
+        // view hierarchy transitions. They should be as if not there.
+        excludeHiddenFragments(state, true);
+
+        TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition);
+        return state;
+    }
+
+    private void updateTransitionEndState(TransitionState state, ArrayList<String> names) {
+        // Find all views that are entering.
+        ArrayList<View> enteringViews = new ArrayList<View>();
+        state.sceneRoot.captureTransitioningViews(enteringViews);
+        enteringViews.removeAll(state.transitioningViews);
+
+        if (names != null) {
+            // find all shared elements.
+            state.namedViews.clear();
+            state.sceneRoot.findNamedViews(state.namedViews);
+            state.namedViews.retainAll(names);
+            if (!state.namedViews.isEmpty()) {
+                enteringViews.removeAll(state.namedViews.values());
+                addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+                // now we know the epicenter of the entering transition.
+                state.mEnteringEpicenterView = state.namedViews.get(names.get(0));
+            }
+        }
+
+        // Add all entering views to the enter transition.
+        addTransitioningViews(state.enterTransition, enteringViews);
+
+        // Don't allow capturing state for the newly-hidden fragments.
+        excludeHiddenFragments(state, false);
+
+        // Allow capturing state for the newly-shown fragments
+        includeVisibleFragments(state.excludingTransition);
+    }
+
+    private void addTransitioningViews(Transition transition, Collection<View> views) {
+        if (views.isEmpty()) {
+            // Add a view so that we can modify the valid views at the end of the
+            // fragment transaction.
+            transition.addTarget(new View(mManager.mActivity));
+        } else {
+            for (View view : views) {
+                transition.addTarget(view);
+            }
+        }
+    }
+
+    private void excludeHiddenFragments(TransitionState state, boolean forceExclude) {
+        if (mManager.mAdded != null) {
+            for (int i = 0; i < mManager.mAdded.size(); i++) {
+                Fragment fragment = mManager.mAdded.get(i);
+                if (fragment.mView != null && fragment.mHidden
+                        && (forceExclude || !state.hiddenViews.contains(fragment.mView))) {
+                    state.excludingTransition.excludeTarget(fragment.mView, true);
+                    state.hiddenViews.add(fragment.mView);
+                }
+            }
+        }
+        if (forceExclude && state.hiddenViews.isEmpty()) {
+            state.excludingTransition.excludeTarget(new View(mManager.mActivity), true);
+        }
+    }
+
+    private void includeVisibleFragments(Transition transition) {
+        if (mManager.mAdded != null) {
+            for (int i = 0; i < mManager.mAdded.size(); i++) {
+                Fragment fragment = mManager.mAdded.get(i);
+                if (fragment.mView != null && !fragment.mHidden) {
+                    transition.excludeTarget(fragment.mView, false);
+                }
+            }
+        }
+    }
+
+    private static void setEpicenter(Transition transition, View view) {
+        final Rect epicenter = new Rect();
+        view.getBoundsOnScreen(epicenter);
+
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return epicenter;
+            }
+        });
+    }
+
+    private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            private Rect mEpicenter;
+
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                if (mEpicenter == null && state.mEnteringEpicenterView != null) {
+                    mEpicenter = new Rect();
+                    state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter);
+                }
+                return mEpicenter;
+            }
+        });
+    }
+
+    public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) {
         if (FragmentManagerImpl.DEBUG) {
             Log.v(TAG, "popFromBackStack: " + this);
             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -698,6 +1010,12 @@
             pw.flush();
         }
 
+        if (state == null) {
+            state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames);
+        } else {
+            setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
+        }
+
         bumpBackStackNesting(-1);
 
         Op op = mTail;
@@ -709,7 +1027,8 @@
                     mManager.removeFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition),
                             mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
                     if (f != null) {
@@ -719,42 +1038,48 @@
                                 mTransitionStyle);
                     }
                     if (op.removed != null) {
-                        for (int i=0; i<op.removed.size(); i++) {
+                        for (int i = 0; i < op.removed.size(); i++) {
                             Fragment old = op.removed.get(i);
                             old.mNextAnim = op.popEnterAnim;
                             mManager.addFragment(old, false);
                         }
                     }
-                } break;
+                }
+                break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.addFragment(f, false);
-                } break;
+                }
+                break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.showFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popExitAnim;
                     mManager.hideFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.attachFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popExitAnim;
                     mManager.detachFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
@@ -766,12 +1091,39 @@
         if (doStateMove) {
             mManager.moveToState(mManager.mCurState,
                     FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
+            if (state != null) {
+                updateTransitionEndState(state, mSharedElementSourceNames);
+                state = null;
+            }
         }
 
         if (mIndex >= 0) {
             mManager.freeBackStackIndex(mIndex);
             mIndex = -1;
         }
+        return state;
+    }
+
+    private static void setNameOverride(Transition transition, String source, String target) {
+        ArrayMap<String, String> overrides = transition.getNameOverrides();
+        for (int index = 0; index < overrides.size(); index++) {
+            if (source.equals(overrides.valueAt(index))) {
+                overrides.setValueAt(index, target);
+                return;
+            }
+        }
+        overrides.put(source, target);
+    }
+
+    private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (sourceNames != null) {
+            for (int i = 0; i < sourceNames.size(); i++) {
+                String source = sourceNames.get(i);
+                String target = targetNames.get(i);
+                setNameOverride(state.excludingTransition, source, target);
+            }
+        }
     }
 
     public String getName() {
@@ -789,4 +1141,16 @@
     public boolean isEmpty() {
         return mNumOp == 0;
     }
+
+    public class TransitionState {
+        public ArrayList<View> hiddenViews = new ArrayList<View>();
+        public ArrayList<View> transitioningViews = new ArrayList<View>();
+        public ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
+        public Transition exitTransition;
+        public Transition sharedElementTransition;
+        public Transition enterTransition;
+        public TransitionSet excludingTransition;
+        public ViewGroup sceneRoot;
+        public View mEnteringEpicenterView;
+    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c5190d3..425a140 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;
@@ -113,9 +115,9 @@
 import android.os.storage.StorageManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.service.fingerprint.IFingerprintService;
 import android.service.fingerprint.FingerprintManager;
-import android.service.fingerprint.FingerprintManagerReceiver;
-import android.service.fingerprint.FingerprintService;
+import android.telecomm.TelecommManager;
 import android.telephony.TelephonyManager;
 import android.content.ClipboardManager;
 import android.util.AndroidRuntimeException;
@@ -140,6 +142,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.appwidget.IAppWidgetService.Stub;
 import com.android.internal.os.IDropBoxManagerService;
+import com.android.internal.telecomm.ITelecommService;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -462,11 +465,6 @@
                     return new KeyguardManager();
                 }});
 
-        registerService(FINGERPRINT_SERVICE, new ServiceFetcher() {
-            public Object createService(ContextImpl ctx) {
-                return new FingerprintManager(ctx);
-            }});
-
         registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
@@ -515,6 +513,9 @@
                 public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(POWER_SERVICE);
                     IPowerManager service = IPowerManager.Stub.asInterface(b);
+                    if (service == null) {
+                        Log.wtf(TAG, "Failed to get power manager service.");
+                    }
                     return new PowerManager(ctx.getOuterContext(),
                             service, ctx.mMainThread.getHandler());
                 }});
@@ -552,6 +553,13 @@
                     return new TelephonyManager(ctx.getOuterContext());
                 }});
 
+        registerService(TELECOMM_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    IBinder b = ServiceManager.getService(TELECOMM_SERVICE);
+                    return new TelecommManager(ctx.getOuterContext(),
+                            ITelecommService.Stub.asInterface(b));
+                }});
+
         registerService(UI_MODE_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     return new UiModeManager();
@@ -654,6 +662,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);
@@ -672,6 +687,7 @@
                 return new MediaSessionManager(ctx);
             }
         });
+
         registerService(TRUST_SERVICE, new ServiceFetcher() {
             public Object createService(ContextImpl ctx) {
                 IBinder b = ServiceManager.getService(TRUST_SERVICE);
@@ -679,6 +695,14 @@
             }
         });
 
+        registerService(FINGERPRINT_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder binder = ServiceManager.getService(FINGERPRINT_SERVICE);
+                IFingerprintService service = IFingerprintService.Stub.asInterface(binder);
+                return new FingerprintManager(ctx.getOuterContext(), service);
+            }
+        });
+
         registerService(TV_INPUT_SERVICE, new ServiceFetcher() {
             public Object createService(ContextImpl ctx) {
                 IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
@@ -1747,7 +1771,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.
         }
@@ -2168,10 +2193,10 @@
                     || overrideConfiguration != null
                     || (compatInfo != null && compatInfo.applicationScale
                             != resources.getCompatibilityInfo().applicationScale)) {
-                resources = mResourcesManager.getTopLevelResources(
-                        packageInfo.getResDir(), packageInfo.getOverlayDirs(),
-                        packageInfo.getApplicationInfo().sharedLibraryFiles,
-                        displayId, overrideConfiguration, compatInfo, activityToken);
+                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
+                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
+                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
+                        overrideConfiguration, compatInfo, activityToken);
             }
         }
         mResources = resources;
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 779e3de..6c71bb1 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -55,6 +55,7 @@
     private boolean mIsExitTransitionComplete;
     private boolean mIsReadyForTransition;
     private Bundle mSharedElementsBundle;
+    private boolean mWasOpaque;
 
     public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
             ArrayList<String> sharedElementNames, boolean isReturning) {
@@ -66,23 +67,36 @@
         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) {
+    public void viewInstancesReady(ArrayList<String> accepted, ArrayList<View> localViews) {
         if (mIsReadyForTransition) {
             return;
         }
-        super.viewsReady(accepted, localNames);
+        viewsReady(mapSharedElements(accepted, localViews));
+    }
 
+    public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
+        if (mIsReadyForTransition) {
+            return;
+        }
+
+        viewsReady(mapNamedElements(accepted, localNames));
+    }
+
+    @Override
+    protected void viewsReady(ArrayMap<String, View> sharedElements) {
+        super.viewsReady(sharedElements);
         mIsReadyForTransition = true;
         if (mIsReturning) {
             mHandler = new Handler() {
@@ -103,9 +117,37 @@
         }
     }
 
+    private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
+            ArrayList<String> localNames) {
+        ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+        getDecor().findNamedViews(sharedElements);
+        if (accepted != null) {
+            for (int i = 0; i < localNames.size(); i++) {
+                String localName = localNames.get(i);
+                String acceptedName = accepted.get(i);
+                if (localName != null && !localName.equals(acceptedName)) {
+                    View view = sharedElements.remove(localName);
+                    if (view != null) {
+                        sharedElements.put(acceptedName, view);
+                    }
+                }
+            }
+        }
+        return sharedElements;
+    }
+
     private void sendSharedElementDestination() {
         ViewGroup decor = getDecor();
-        if (!decor.isLayoutRequested()) {
+        boolean allReady = !decor.isLayoutRequested();
+        if (allReady) {
+            for (int i = 0; i < mSharedElements.size(); i++) {
+                if (mSharedElements.get(i).isLayoutRequested()) {
+                    allReady = false;
+                    break;
+                }
+            }
+        }
+        if (allReady) {
             Bundle state = captureSharedElementState();
             mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
         } else {
@@ -114,10 +156,15 @@
                         @Override
                         public boolean onPreDraw() {
                             getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                            Bundle state = captureSharedElementState();
+                            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
                             return true;
                         }
                     });
         }
+        if (allowOverlappingTransitions()) {
+            startEnterTransitionOnly();
+        }
     }
 
     private static SharedElementListener getListener(Activity activity, boolean isReturning) {
@@ -176,7 +223,7 @@
     protected void prepareEnter() {
         mActivity.overridePendingTransition(0, 0);
         if (!mIsReturning) {
-            mActivity.convertToTranslucent(null, null);
+            mWasOpaque = mActivity.convertToTranslucent(null, null);
             Drawable background = getDecor().getBackground();
             if (background != null) {
                 getWindow().setBackgroundDrawable(null);
@@ -206,24 +253,6 @@
         }
     }
 
-    protected void onTakeSharedElements() {
-        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
-            return;
-        }
-        final Bundle sharedElementState = mSharedElementsBundle;
-        mSharedElementsBundle = null;
-        getDecor().getViewTreeObserver()
-                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
-                        startSharedElementTransition(sharedElementState);
-                        return false;
-                    }
-                });
-        getDecor().invalidate();
-    }
-
     private void startSharedElementTransition(Bundle sharedElementState) {
         setEpicenter();
         // Remove rejected shared elements
@@ -241,7 +270,7 @@
                 setSharedElementState(sharedElementState, sharedElementSnapshots);
         requestLayoutForSharedElements();
 
-        boolean startEnterTransition = allowOverlappingTransitions();
+        boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
         boolean startSharedElementTransition = true;
         Transition transition = beginTransition(startEnterTransition, startSharedElementTransition);
 
@@ -257,6 +286,36 @@
         mResultReceiver = null; // all done sending messages.
     }
 
+    @Override
+    protected void stripOffscreenViews() {
+        setViewVisibility(mTransitioningViews, View.VISIBLE);
+        super.stripOffscreenViews();
+        setViewVisibility(mTransitioningViews, View.INVISIBLE);
+    }
+
+    private void onTakeSharedElements() {
+        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
+            return;
+        }
+        final Bundle sharedElementState = mSharedElementsBundle;
+        mSharedElementsBundle = null;
+        getDecor().getViewTreeObserver()
+                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                        startTransition(new Runnable() {
+                            @Override
+                            public void run() {
+                                startSharedElementTransition(sharedElementState);
+                            }
+                        });
+                        return false;
+                    }
+                });
+        getDecor().invalidate();
+    }
+
     private void requestLayoutForSharedElements() {
         int numSharedElements = mSharedElements.size();
         for (int i = 0; i < numSharedElements; i++) {
@@ -273,7 +332,10 @@
         Transition viewsTransition = null;
         if (startEnterTransition && !mTransitioningViews.isEmpty()) {
             viewsTransition = configureTransition(getViewsTransition());
-            viewsTransition = addTargets(viewsTransition, mTransitioningViews);
+            if (viewsTransition != null) {
+                stripOffscreenViews();
+                viewsTransition = addTargets(viewsTransition, mTransitioningViews);
+            }
         }
 
         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
@@ -281,9 +343,10 @@
             if (transition == null) {
                 sharedElementTransitionStarted();
             } else {
-                transition.addListener(new Transition.TransitionListenerAdapter() {
+                transition.addListener(new ContinueTransitionListener() {
                     @Override
                     public void onTransitionStart(Transition transition) {
+                        super.onTransitionStart(transition);
                         transition.removeListener(this);
                         sharedElementTransitionStarted();
                     }
@@ -291,12 +354,17 @@
             }
         }
         if (transition != null) {
+            if (sharedElementTransition == null) {
+                transition.addListener(new ContinueTransitionListener());
+            }
             TransitionManager.beginDelayedTransition(getDecor(), transition);
             if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
                 mSharedElements.get(0).invalidate();
             } else if (startEnterTransition && !mTransitioningViews.isEmpty()) {
                 mTransitioningViews.get(0).invalidate();
             }
+        } else {
+            transitionStarted();
         }
         return transition;
     }
@@ -315,7 +383,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) {
@@ -350,7 +418,9 @@
 
     private void makeOpaque() {
         if (!mHasStopped && mActivity != null) {
-            mActivity.convertFromTranslucent();
+            if (mWasOpaque) {
+                mActivity.convertFromTranslucent();
+            }
             mActivity = null;
         }
     }
@@ -387,11 +457,21 @@
 
     protected void onRemoteExitTransitionComplete() {
         if (!allowOverlappingTransitions()) {
-            boolean startEnterTransition = true;
-            boolean startSharedElementTransition = false;
-            Transition transition = beginTransition(startEnterTransition,
-                    startSharedElementTransition);
-            startEnterTransition(transition);
+            startEnterTransitionOnly();
         }
     }
+
+    private void startEnterTransitionOnly() {
+        startTransition(new Runnable() {
+            @Override
+            public void run() {
+                setEpicenter();
+                boolean startEnterTransition = true;
+                boolean startSharedElementTransition = false;
+                Transition transition = beginTransition(startEnterTransition,
+                        startSharedElementTransition);
+                startEnterTransition(transition);
+            }
+        });
+    }
 }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index ba1638ff..e38f527 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -19,6 +19,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Intent;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -26,7 +27,9 @@
 import android.os.Message;
 import android.transition.Transition;
 import android.transition.TransitionManager;
+import android.util.ArrayMap;
 import android.view.View;
+import android.view.ViewGroupOverlay;
 import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
@@ -60,14 +63,16 @@
 
     private boolean mIsHidden;
 
-    private boolean mExitTransitionStarted;
-
     private Bundle mExitSharedElementBundle;
 
+    private ArrayList<View> mSharedElementSnapshots;
+
     public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
-            ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
-        super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning),
+            ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
+        super(activity.getWindow(), names, getListener(activity, isReturning),
                 isReturning);
+        viewsReady(mapSharedElements(accepted, mapped));
+        stripOffscreenViews();
         mIsBackgroundReady = !isReturning;
         mActivity = activity;
     }
@@ -99,37 +104,92 @@
             case MSG_START_EXIT_TRANSITION:
                 startExit();
                 break;
-            case MSG_ACTIVITY_STOPPED:
-                setViewVisibility(mTransitioningViews, View.VISIBLE);
-                setViewVisibility(mSharedElements, View.VISIBLE);
-                mIsHidden = true;
-                break;
             case MSG_SHARED_ELEMENT_DESTINATION:
                 mExitSharedElementBundle = resultData;
-                if (mExitTransitionStarted) {
+                sharedElementExitBack();
+                break;
+        }
+    }
+
+    public void resetViews() {
+        setViewVisibility(mTransitioningViews, View.VISIBLE);
+        setViewVisibility(mSharedElements, View.VISIBLE);
+        mIsHidden = true;
+    }
+
+    private void sharedElementExitBack() {
+        if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
+            startTransition(new Runnable() {
+                public void run() {
                     startSharedElementExit();
                 }
-                break;
+            });
         }
     }
 
     private void startSharedElementExit() {
-        if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
-            Transition transition = getSharedElementExitTransition();
-            TransitionManager.beginDelayedTransition(getDecor(), transition);
-            ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
-                    mSharedElementNames);
-            setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
+        Transition transition = getSharedElementExitTransition();
+        final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
+                mSharedElementNames);
+        mSharedElementSnapshots = createSnapshots(mExitSharedElementBundle, mSharedElementNames);
+        transition.addListener(new Transition.TransitionListenerAdapter() {
+            @Override
+            public void onTransitionEnd(Transition transition) {
+                transition.removeListener(this);
+                setViewVisibility(mSharedElements, View.INVISIBLE);
+                ViewGroupOverlay overlay = getDecor().getOverlay();
+                if (mSharedElementSnapshots != null) {
+                    for (int i = 0; i < mSharedElementSnapshots.size(); i++) {
+                        overlay.add(mSharedElementSnapshots.get(i));
+                    }
+                }
+            }
+        });
+        getDecor().getViewTreeObserver()
+                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
+                        return true;
+                    }
+                });
+        TransitionManager.beginDelayedTransition(getDecor(), transition);
+        getDecor().invalidate();
+    }
+
+    private static ArrayList<View> copySnapshots(ArrayList<View> snapshots) {
+        ArrayList<View> copy = new ArrayList<View>(snapshots.size());
+        for (int i = 0; i < snapshots.size(); i++) {
+            View view = snapshots.get(i);
+            View viewCopy = new View(view.getContext());
+            viewCopy.setBackground(view.getBackground());
+            copy.add(viewCopy);
         }
+        return copy;
     }
 
     private void hideSharedElements() {
-        setViewVisibility(mSharedElements, View.INVISIBLE);
+        if (!mIsHidden) {
+            setViewVisibility(mSharedElements, View.INVISIBLE);
+        }
+        if (mSharedElementSnapshots != null) {
+            ViewGroupOverlay overlay = getDecor().getOverlay();
+            for (int i = 0; i < mSharedElementSnapshots.size(); i++) {
+                overlay.remove(mSharedElementSnapshots.get(i));
+            }
+            mSharedElementSnapshots = null;
+        }
         finishIfNecessary();
     }
 
     public void startExit() {
-        beginTransitions();
+        startTransition(new Runnable() {
+            @Override
+            public void run() {
+                beginTransitions();
+            }
+        });
         setViewVisibility(mTransitioningViews, View.INVISIBLE);
     }
 
@@ -138,8 +198,7 @@
             @Override
             public void handleMessage(Message msg) {
                 mIsCanceled = true;
-                mActivity.finish();
-                mActivity = null;
+                finish();
             }
         };
         mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
@@ -159,29 +218,19 @@
                 }
             }
         }, options);
-        Transition sharedElementTransition = mSharedElements.isEmpty()
-                ? null : getSharedElementTransition();
-        if (sharedElementTransition == null) {
-            sharedElementTransitionComplete();
-        }
-        Transition transition = mergeTransitions(sharedElementTransition, getExitTransition());
-        if (transition == null) {
-            mExitTransitionStarted = true;
-        } else {
+        startTransition(new Runnable() {
+            @Override
+            public void run() {
+                startExitTransition();
+            }
+        });
+    }
+
+    private void startExitTransition() {
+        Transition transition = getExitTransition();
+        if (transition != null) {
             TransitionManager.beginDelayedTransition(getDecor(), transition);
             setViewVisibility(mTransitioningViews, View.INVISIBLE);
-            getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
-                    mExitTransitionStarted = true;
-                    if (mExitSharedElementBundle != null) {
-                        startSharedElementExit();
-                    }
-                    notifyComplete();
-                    return true;
-                }
-            });
         }
     }
 
@@ -199,7 +248,7 @@
                     }
                 }
             });
-            mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
+            mBackgroundAnimator.setDuration(getFadeDuration());
             mBackgroundAnimator.start();
         }
     }
@@ -212,9 +261,10 @@
         if (viewsTransition == null) {
             exitTransitionComplete();
         } else {
-            viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
+            viewsTransition.addListener(new ContinueTransitionListener() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
+                    transition.removeListener(this);
                     exitTransitionComplete();
                     if (mIsHidden) {
                         setViewVisibility(mTransitioningViews, View.VISIBLE);
@@ -238,9 +288,10 @@
         if (sharedElementTransition == null) {
             sharedElementTransitionComplete();
         } else {
-            sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+            sharedElementTransition.addListener(new ContinueTransitionListener() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
+                    transition.removeListener(this);
                     sharedElementTransitionComplete();
                     if (mIsHidden) {
                         setViewVisibility(mSharedElements, View.VISIBLE);
@@ -257,7 +308,6 @@
         Transition viewsTransition = getExitTransition();
 
         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
-        mExitTransitionStarted = true;
         if (transition != null) {
             TransitionManager.beginDelayedTransition(getDecor(), transition);
         }
@@ -269,15 +319,31 @@
     }
 
     protected boolean isReadyToNotify() {
-        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady
-                && mExitTransitionStarted;
+        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
     }
 
     private void sharedElementTransitionComplete() {
-        mSharedElementBundle = captureSharedElementState();
+        mSharedElementBundle = mExitSharedElementBundle == null
+                ? captureSharedElementState() : captureExitSharedElementsState();
         notifyComplete();
     }
 
+    private Bundle captureExitSharedElementsState() {
+        Bundle bundle = new Bundle();
+        Rect bounds = new Rect();
+        for (int i = 0; i < mSharedElementNames.size(); i++) {
+            String name = mSharedElementNames.get(i);
+            Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
+            if (sharedElementState != null) {
+                bundle.putBundle(name, sharedElementState);
+            } else {
+                View view = mSharedElements.get(i);
+                captureSharedElementState(view, name, bundle, bounds);
+            }
+        }
+        return bundle;
+    }
+
     protected void notifyComplete() {
         if (isReadyToNotify()) {
             if (!mSharedElementNotified) {
@@ -294,17 +360,32 @@
     }
 
     private void finishIfNecessary() {
-        if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty()
-                || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) {
-            mActivity.finish();
-            mActivity.overridePendingTransition(0, 0);
-            mActivity = null;
+        if (mIsReturning && mExitNotified && mActivity != null && mSharedElementSnapshots == null) {
+            finish();
         }
         if (!mIsReturning && mExitNotified) {
             mActivity = null; // don't need it anymore
         }
     }
 
+    private void finish() {
+        mActivity.mActivityTransitionState.clear();
+        // Clear the state so that we can't hold any references accidentally and leak memory.
+        mHandler.removeMessages(MSG_CANCEL);
+        mHandler = null;
+        mActivity.finish();
+        mActivity.overridePendingTransition(0, 0);
+        mActivity = null;
+        mSharedElementBundle = null;
+        if (mBackgroundAnimator != null) {
+            mBackgroundAnimator.cancel();
+            mBackgroundAnimator = null;
+        }
+        mExitSharedElementBundle = null;
+        mSharedElementSnapshots = null;
+        clearState();
+    }
+
     @Override
     protected Transition getViewsTransition() {
         if (mIsReturning) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6c0d379..2ff3d57 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1104,7 +1105,15 @@
      * inflation.  Maybe this should become a public API. Note sure.
      */
     public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
-        return mActivity.getLayoutInflater();
+        // Newer platform versions use the child fragment manager's LayoutInflaterFactory.
+        if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+            LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity);
+            getChildFragmentManager(); // Init if needed; use raw implementation below.
+            result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory());
+            return result;
+        } else {
+            return mActivity.getLayoutInflater();
+        }
     }
     
     /**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 76f9d97..a97fa650 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Bundle;
@@ -27,11 +28,13 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.AttributeSet;
 import android.util.DebugUtils;
 import android.util.Log;
 import android.util.LogWriter;
 import android.util.SparseArray;
 import android.util.SuperNotCalledException;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -397,7 +400,7 @@
 /**
  * Container for fragments associated with an activity.
  */
-final class FragmentManagerImpl extends FragmentManager {
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
     static boolean DEBUG = false;
     static final String TAG = "FragmentManager";
     
@@ -432,7 +435,7 @@
     boolean mDestroyed;
     String mNoTransactionsBecause;
     boolean mHavePendingDeferredStart;
-    
+
     // Temporary vars for state save and restore.
     Bundle mStateBundle = null;
     SparseArray<Parcelable> mStateArray = null;
@@ -1494,7 +1497,7 @@
                 return false;
             }
             final BackStackRecord bss = mBackStack.remove(last);
-            bss.popFromBackStack(true);
+            bss.popFromBackStack(true, null);
             reportBackStackChanged();
         } else {
             int index = -1;
@@ -1538,9 +1541,10 @@
                 states.add(mBackStack.remove(i));
             }
             final int LAST = states.size()-1;
+            BackStackRecord.TransitionState state = null;
             for (int i=0; i<=LAST; i++) {
                 if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
-                states.get(i).popFromBackStack(i == LAST);
+                state = states.get(i).popFromBackStack(i == LAST, state);
             }
             reportBackStackChanged();
         }
@@ -2051,4 +2055,100 @@
         }
         return animAttr;
     }
+
+    @Override
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        if (!"fragment".equals(name)) {
+            return null;
+        }
+
+        String fname = attrs.getAttributeValue(null, "class");
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
+        if (fname == null) {
+            fname = a.getString(com.android.internal.R.styleable.Fragment_name);
+        }
+        int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
+        String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
+        a.recycle();
+
+        int containerId = parent != null ? parent.getId() : 0;
+        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Must specify unique android:id, android:tag, or have a parent with"
+                    + " an id for " + fname);
+        }
+
+        // If we restored from a previous state, we may already have
+        // instantiated this fragment from the state and should use
+        // that instance instead of making a new one.
+        Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
+        if (fragment == null && tag != null) {
+            fragment = findFragmentByTag(tag);
+        }
+        if (fragment == null && containerId != View.NO_ID) {
+            fragment = findFragmentById(containerId);
+        }
+
+        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+                + Integer.toHexString(id) + " fname=" + fname
+                + " existing=" + fragment);
+        if (fragment == null) {
+            fragment = Fragment.instantiate(context, fname);
+            fragment.mFromLayout = true;
+            fragment.mFragmentId = id != 0 ? id : containerId;
+            fragment.mContainerId = containerId;
+            fragment.mTag = tag;
+            fragment.mInLayout = true;
+            fragment.mFragmentManager = this;
+            fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+            addFragment(fragment, true);
+        } else if (fragment.mInLayout) {
+            // A fragment already exists and it is not one we restored from
+            // previous state.
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Duplicate id 0x" + Integer.toHexString(id)
+                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+                    + " with another fragment for " + fname);
+        } else {
+            // This fragment was retained from a previous instance; get it
+            // going now.
+            fragment.mInLayout = true;
+            // If this fragment is newly instantiated (either right now, or
+            // from last saved state), then give it the attributes to
+            // initialize itself.
+            if (!fragment.mRetaining) {
+                fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+            }
+        }
+
+        // If we haven't finished entering the CREATED state ourselves yet,
+        // push the inflated child fragment along.
+        if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
+            moveToState(fragment, Fragment.CREATED, 0, 0, false);
+        } else {
+            moveToState(fragment);
+        }
+
+        if (fragment.mView == null) {
+            throw new IllegalStateException("Fragment " + fname
+                    + " did not create a view.");
+        }
+        if (id != 0) {
+            fragment.mView.setId(id);
+        }
+        if (fragment.mView.getTag() == null) {
+            fragment.mView.setTag(tag);
+        }
+        return fragment.mView;
+    }
+
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        return null;
+    }
+
+    LayoutInflater.Factory2 getLayoutInflaterFactory() {
+        return this;
+    }
 }
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 6e99899..b6ea3c3 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -1,5 +1,8 @@
 package android.app;
 
+import android.util.Pair;
+import android.view.View;
+
 /**
  * API for performing a set of Fragment operations.
  *
@@ -169,6 +172,36 @@
     public abstract FragmentTransaction setTransition(int transit);
 
     /**
+     * Set a {@link android.transition.Transition} resource id to use with this transaction.
+     * <var>transitionId</var> will be played for fragments when going forward and when popping
+     * the back stack.
+     * @param sceneRootId The ID of the element acting as the scene root for the transition.
+     *                    This should be a ViewGroup containing all Fragments in the transaction.
+     * @param transitionId The resource ID for the Transition used during the Fragment transaction.
+     */
+    public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden
+     * Fragment to a View from a shown or added Fragment.
+     * <var>sharedElement</var> must have a unique transitionName in the View hierarchy.
+     * @param sharedElement A View in a disappearing Fragment to match with a View in an
+     *                      appearing Fragment.
+     * @param name The transitionName for a View in an appearing Fragment to match to the shared
+     *             element.
+     */
+    public abstract FragmentTransaction setSharedElement(View sharedElement, String name);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden
+     * Fragments to a Views from a shown or added Fragments. Views in
+     * <var>sharedElements</var> must have unique transitionNames in the View hierarchy.
+     * @param sharedElements Pairs of Views in disappearing Fragments to transitionNames in
+     *                       appearing Fragments.
+     */
+    public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements);
+
+    /**
      * Set a custom style resource that will be used for resolving transit
      * animations.
      */
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..b630278 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;
 
@@ -428,6 +429,9 @@
     public IBinder getHomeActivityToken() throws RemoteException;
 
     /** @hide */
+    public void startLockTaskModeOnCurrent() throws RemoteException;
+
+    /** @hide */
     public void startLockTaskMode(int taskId) throws RemoteException;
 
     /** @hide */
@@ -437,6 +441,9 @@
     public void stopLockTaskMode() throws RemoteException;
 
     /** @hide */
+    public void stopLockTaskModeOnCurrent() throws RemoteException;
+
+    /** @hide */
     public boolean isInLockTaskMode() throws RemoteException;
 
     /** @hide */
@@ -743,4 +750,6 @@
     int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
     int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
     int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
+    int START_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+221;
+    int STOP_LOCK_TASK_BY_CURRENT = IBinder.FIRST_CALL_TRANSACTION+222;
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b78b9c9..c583998 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -188,6 +188,9 @@
             endPerformanceSnapshot();
         }
         if (mPerfMetrics != null) {
+            if (results == null) {
+                results = new Bundle();
+            }
             results.putAll(mPerfMetrics);
         }
         if (mUiAutomation != null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 3ae8bfc..065e88d 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -16,8 +16,8 @@
 
 package android.app;
 
+import android.text.TextUtils;
 import android.util.ArrayMap;
-import com.android.internal.util.ArrayUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -52,6 +52,8 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 
 final class IntentReceiverLeaked extends AndroidRuntimeException {
@@ -79,6 +81,8 @@
     final String mPackageName;
     private final String mAppDir;
     private final String mResDir;
+    private final String[] mSplitAppDirs;
+    private final String[] mSplitResDirs;
     private final String[] mOverlayDirs;
     private final String[] mSharedLibraries;
     private final String mDataDir;
@@ -116,13 +120,14 @@
     public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
             CompatibilityInfo compatInfo, ClassLoader baseLoader,
             boolean securityViolation, boolean includeCode) {
+        final int myUid = Process.myUid();
         mActivityThread = activityThread;
         mApplicationInfo = aInfo;
         mPackageName = aInfo.packageName;
         mAppDir = aInfo.sourceDir;
-        final int myUid = Process.myUid();
-        mResDir = aInfo.uid == myUid ? aInfo.sourceDir
-                : aInfo.publicSourceDir;
+        mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
+        mSplitAppDirs = aInfo.splitSourceDirs;
+        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
         mOverlayDirs = aInfo.resourceDirs;
         if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
             aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid),
@@ -149,6 +154,8 @@
         mPackageName = "android";
         mAppDir = null;
         mResDir = null;
+        mSplitAppDirs = null;
+        mSplitResDirs = null;
         mOverlayDirs = null;
         mSharedLibraries = null;
         mDataDir = null;
@@ -214,53 +221,6 @@
         return ai.sharedLibraryFiles;
     }
 
-    /**
-     * Combines two arrays (of library names) such that they are
-     * concatenated in order but are devoid of duplicates. The
-     * result is a single string with the names of the libraries
-     * separated by colons, or <code>null</code> if both lists
-     * were <code>null</code> or empty.
-     *
-     * @param list1 null-ok; the first list
-     * @param list2 null-ok; the second list
-     * @return null-ok; the combination
-     */
-    private static String combineLibs(String[] list1, String[] list2) {
-        StringBuilder result = new StringBuilder(300);
-        boolean first = true;
-
-        if (list1 != null) {
-            for (String s : list1) {
-                if (first) {
-                    first = false;
-                } else {
-                    result.append(':');
-                }
-                result.append(s);
-            }
-        }
-
-        // Only need to check for duplicates if list1 was non-empty.
-        boolean dupCheck = !first;
-
-        if (list2 != null) {
-            for (String s : list2) {
-                if (dupCheck && ArrayUtils.contains(list1, s)) {
-                    continue;
-                }
-
-                if (first) {
-                    first = false;
-                } else {
-                    result.append(':');
-                }
-                result.append(s);
-            }
-        }
-
-        return result.toString();
-    }
-
     public ClassLoader getClassLoader() {
         synchronized (this) {
             if (mClassLoader != null) {
@@ -268,8 +228,15 @@
             }
 
             if (mIncludeCode && !mPackageName.equals("android")) {
-                String zip = mAppDir;
-                String libraryPath = mLibDir;
+                final ArrayList<String> zipPaths = new ArrayList<>();
+                final ArrayList<String> libPaths = new ArrayList<>();
+
+                zipPaths.add(mAppDir);
+                if (mSplitAppDirs != null) {
+                    Collections.addAll(zipPaths, mSplitAppDirs);
+                }
+
+                libPaths.add(mLibDir);
 
                 /*
                  * The following is a bit of a hack to inject
@@ -280,50 +247,70 @@
                  * concatenation of both apps' shared library lists.
                  */
 
-                String instrumentationAppDir =
-                        mActivityThread.mInstrumentationAppDir;
-                String instrumentationAppLibraryDir =
-                        mActivityThread.mInstrumentationAppLibraryDir;
-                String instrumentationAppPackage =
-                        mActivityThread.mInstrumentationAppPackage;
-                String instrumentedAppDir =
-                        mActivityThread.mInstrumentedAppDir;
-                String instrumentedAppLibraryDir =
-                        mActivityThread.mInstrumentedAppLibraryDir;
+                String instrumentationPackageName = mActivityThread.mInstrumentationPackageName;
+                String instrumentationAppDir = mActivityThread.mInstrumentationAppDir;
+                String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs;
+                String instrumentationLibDir = mActivityThread.mInstrumentationLibDir;
+
+                String instrumentedAppDir = mActivityThread.mInstrumentedAppDir;
+                String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs;
+                String instrumentedLibDir = mActivityThread.mInstrumentedLibDir;
                 String[] instrumentationLibs = null;
 
                 if (mAppDir.equals(instrumentationAppDir)
                         || mAppDir.equals(instrumentedAppDir)) {
-                    zip = instrumentationAppDir + ":" + instrumentedAppDir;
-                    libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir;
-                    if (! instrumentedAppDir.equals(instrumentationAppDir)) {
-                        instrumentationLibs =
-                            getLibrariesFor(instrumentationAppPackage);
+                    zipPaths.clear();
+                    zipPaths.add(instrumentationAppDir);
+                    if (instrumentationSplitAppDirs != null) {
+                        Collections.addAll(zipPaths, instrumentationSplitAppDirs);
+                    }
+                    zipPaths.add(instrumentedAppDir);
+                    if (instrumentedSplitAppDirs != null) {
+                        Collections.addAll(zipPaths, instrumentedSplitAppDirs);
+                    }
+
+                    libPaths.clear();
+                    libPaths.add(instrumentationLibDir);
+                    libPaths.add(instrumentedLibDir);
+
+                    if (!instrumentedAppDir.equals(instrumentationAppDir)) {
+                        instrumentationLibs = getLibrariesFor(instrumentationPackageName);
                     }
                 }
 
-                if ((mSharedLibraries != null) ||
-                        (instrumentationLibs != null)) {
-                    zip =
-                        combineLibs(mSharedLibraries, instrumentationLibs)
-                        + ':' + zip;
+                if (mSharedLibraries != null) {
+                    for (String lib : mSharedLibraries) {
+                        if (!zipPaths.contains(lib)) {
+                            zipPaths.add(0, lib);
+                        }
+                    }
                 }
 
+                if (instrumentationLibs != null) {
+                    for (String lib : instrumentationLibs) {
+                        if (!zipPaths.contains(lib)) {
+                            zipPaths.add(0, lib);
+                        }
+                    }
+                }
+
+                final String zip = TextUtils.join(File.pathSeparator, zipPaths);
+                final String lib = TextUtils.join(File.pathSeparator, libPaths);
+
                 /*
                  * With all the combination done (if necessary, actually
                  * create the class loader.
                  */
 
                 if (ActivityThread.localLOGV)
-                    Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath);
+                    Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + lib);
 
                 // Temporarily disable logging of disk reads on the Looper thread
                 // as this is early and necessary.
                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
 
-                mClassLoader =
-                    ApplicationLoaders.getDefault().getClassLoader(
-                        zip, libraryPath, mBaseClassLoader);
+                mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
+                        mBaseClassLoader);
                 initializeJavaContextClassLoader();
 
                 StrictMode.setThreadPolicy(oldPolicy);
@@ -469,6 +456,14 @@
         return mResDir;
     }
 
+    public String[] getSplitAppDirs() {
+        return mSplitAppDirs;
+    }
+
+    public String[] getSplitResDirs() {
+        return mSplitResDirs;
+    }
+
     public String[] getOverlayDirs() {
         return mOverlayDirs;
     }
@@ -487,7 +482,7 @@
 
     public Resources getResources(ActivityThread mainThread) {
         if (mResources == null) {
-            mResources = mainThread.getTopLevelResources(mResDir, mOverlayDirs,
+            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                     mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
         }
         return mResources;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index debe68a..84f7e5f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2918,16 +2918,16 @@
     /**
      * Helper class for generating large-format notifications that include a large image attachment.
      *
-     * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
+     * Here's how you'd set the <code>BigPictureStyle</code> on a notification:
      * <pre class="prettyprint">
-     * Notification noti = new Notification.BigPictureStyle(
-     *      new Notification.Builder()
-     *         .setContentTitle(&quot;New photo from &quot; + sender.toString())
-     *         .setContentText(subject)
-     *         .setSmallIcon(R.drawable.new_post)
-     *         .setLargeIcon(aBitmap))
-     *      .bigPicture(aBigBitmap)
-     *      .build();
+     * Notification notif = new Notification.Builder(mContext)
+     *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
+     *     .setContentText(subject)
+     *     .setSmallIcon(R.drawable.new_post)
+     *     .setLargeIcon(aBitmap)
+     *     .setStyle(new Notification.BigPictureStyle()
+     *         .bigPicture(aBigBitmap))
+     *     .build();
      * </pre>
      *
      * @see Notification#bigContentView
@@ -3014,16 +3014,16 @@
     /**
      * Helper class for generating large-format notifications that include a lot of text.
      *
-     * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
+     * Here's how you'd set the <code>BigTextStyle</code> on a notification:
      * <pre class="prettyprint">
-     * Notification noti = new Notification.BigTextStyle(
-     *      new Notification.Builder()
-     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
-     *         .setContentText(subject)
-     *         .setSmallIcon(R.drawable.new_mail)
-     *         .setLargeIcon(aBitmap))
-     *      .bigText(aVeryLongString)
-     *      .build();
+     * Notification notif = new Notification.Builder(mContext)
+     *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
+     *     .setContentText(subject)
+     *     .setSmallIcon(R.drawable.new_mail)
+     *     .setLargeIcon(aBitmap)
+     *     .setStyle(new Notification.BigTextStyle()
+     *         .bigText(aVeryLongString))
+     *     .build();
      * </pre>
      *
      * @see Notification#bigContentView
@@ -3108,19 +3108,19 @@
     /**
      * Helper class for generating large-format notifications that include a list of (up to 5) strings.
      *
-     * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
+     * Here's how you'd set the <code>InboxStyle</code> on a notification:
      * <pre class="prettyprint">
-     * Notification noti = new Notification.InboxStyle(
-     *      new Notification.Builder()
-     *         .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
-     *         .setContentText(subject)
-     *         .setSmallIcon(R.drawable.new_mail)
-     *         .setLargeIcon(aBitmap))
-     *      .addLine(str1)
-     *      .addLine(str2)
-     *      .setContentTitle("")
-     *      .setSummaryText(&quot;+3 more&quot;)
-     *      .build();
+     * Notification notif = new Notification.Builder(mContext)
+     *     .setContentTitle(&quot;5 New mails from &quot; + sender.toString())
+     *     .setContentText(subject)
+     *     .setSmallIcon(R.drawable.new_mail)
+     *     .setLargeIcon(aBitmap)
+     *     .setStyle(new Notification.InboxStyle()
+     *         .addLine(str1)
+     *         .addLine(str2)
+     *         .setContentTitle(&quot;&quot;)
+     *         .setSummaryText(&quot;+3 more&quot;))
+     *     .build();
      * </pre>
      *
      * @see Notification#bigContentView
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index a67faa0..3c13115 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -149,9 +149,9 @@
      * @param compatInfo the compability info. Must not be null.
      * @param token the application token for determining stack bounds.
      */
-    public Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs,
-            int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo,
-            IBinder token) {
+    public Resources getTopLevelResources(String resDir, String[] splitResDirs,
+            String[] overlayDirs, String[] libDirs, int displayId,
+            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
         final float scale = compatInfo.applicationScale;
         ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
         Resources r;
@@ -182,6 +182,14 @@
             return null;
         }
 
+        if (splitResDirs != null) {
+            for (String splitResDir : splitResDirs) {
+                if (assets.addAssetPath(splitResDir) == 0) {
+                    return null;
+                }
+            }
+        }
+
         if (overlayDirs != null) {
             for (String idmapPath : overlayDirs) {
                 assets.addOverlayPath(idmapPath);
diff --git a/core/java/android/app/SharedElementListener.java b/core/java/android/app/SharedElementListener.java
index e03e42e..14fbfab 100644
--- a/core/java/android/app/SharedElementListener.java
+++ b/core/java/android/app/SharedElementListener.java
@@ -108,7 +108,7 @@
      * @param names The names of all shared elements transferred from the calling Activity
      *              to the started Activity.
      * @param sharedElements The mapping of shared element names to Views. The best guess
-     *                       will be filled into sharedElements based on the View names.
+     *                       will be filled into sharedElements based on the transitionNames.
      */
     public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {}
 }
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 1015514..ca40436 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -166,6 +166,40 @@
             = "android.app.action.ACTION_PASSWORD_EXPIRING";
 
     /**
+     * Action sent to a device administrator to notify that the device is entering
+     * or exiting lock task mode from an authorized package.  The extra
+     * {@link #EXTRA_LOCK_TASK_ENTERING} will describe whether entering or exiting
+     * the mode.  If entering, the extra {@link #EXTRA_LOCK_TASK_PACKAGE} will describe
+     * the authorized package using lock task mode.
+     *
+     * @see DevicePolicyManager#isLockTaskPermitted
+     *
+     * <p>The calling device admin must be the device owner or profile
+     * owner to receive this broadcast.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LOCK_TASK_CHANGED
+            = "android.app.action.ACTION_LOCK_TASK_CHANGED";
+
+    /**
+     * A boolean describing whether the device is currently entering or exiting
+     * lock task mode.
+     *
+     * @see #ACTION_LOCK_TASK_CHANGED
+     */
+    public static final String EXTRA_LOCK_TASK_ENTERING =
+            "android.app.extra.LOCK_TASK_ENTERING";
+
+    /**
+     * A boolean describing whether the device is currently entering or exiting
+     * lock task mode.
+     *
+     * @see #ACTION_LOCK_TASK_CHANGED
+     */
+    public static final String EXTRA_LOCK_TASK_PACKAGE =
+            "android.app.extra.LOCK_TASK_PACKAGE";
+
+    /**
      * Broadcast Action: This broadcast is sent to indicate that provisioning of a managed profile
      * or managed device has completed successfully.
      *
@@ -222,6 +256,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}.
      */
@@ -335,6 +375,19 @@
     }
 
     /**
+     * Called when a device is entering or exiting lock task mode by a package
+     * authorized by {@link DevicePolicyManager#isLockTaskPermitted(String)}
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param isEnteringLockTask Whether the device is entering or exiting lock task mode.
+     * @param pkg If entering, the authorized package using lock task mode, otherwise null.
+     */
+    public void onLockTaskModeChanged(Context context, Intent intent, boolean isEnteringLockTask,
+            String pkg) {
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -363,6 +416,10 @@
             onPasswordExpiring(context, intent);
         } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) {
             onProfileProvisioningComplete(context, intent);
+        } else if (ACTION_LOCK_TASK_CHANGED.equals(action)) {
+            boolean isEntering = intent.getBooleanExtra(EXTRA_LOCK_TASK_ENTERING, false);
+            String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE);
+            onLockTaskModeChanged(context, intent, isEntering, pkg);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 157b8ec..4351f9d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,6 +26,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 +38,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 +86,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 +129,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 +148,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 +168,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 +236,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 +1290,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 +1397,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;
@@ -1427,12 +1507,11 @@
      *
      * @return false if the certBuffer cannot be parsed or installation is
      *         interrupted, otherwise true
-     * @hide
      */
-    public boolean installCaCert(byte[] certBuffer) {
+    public boolean installCaCert(ComponentName who, byte[] certBuffer) {
         if (mService != null) {
             try {
-                return mService.installCaCert(certBuffer);
+                return mService.installCaCert(who, certBuffer);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -1442,13 +1521,14 @@
 
     /**
      * Uninstalls the given certificate from the list of User CAs, if present.
-     *
-     * @hide
      */
-    public void uninstallCaCert(byte[] certBuffer) {
+    public void uninstallCaCert(ComponentName who, byte[] certBuffer) {
         if (mService != null) {
             try {
-                mService.uninstallCaCert(certBuffer);
+                final String alias = getCaCertAlias(certBuffer);
+                mService.uninstallCaCert(who, alias);
+            } catch (CertificateException e) {
+                Log.w(TAG, "Unable to parse certificate", e);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -1457,10 +1537,8 @@
 
     /**
      * Returns whether there are any user-installed CA certificates.
-     *
-     * @hide
      */
-    public static boolean hasAnyCaCertsInstalled() {
+    public boolean hasAnyCaCertsInstalled() {
         TrustedCertificateStore certStore = new TrustedCertificateStore();
         Set<String> aliases = certStore.userAliases();
         return aliases != null && !aliases.isEmpty();
@@ -1468,18 +1546,10 @@
 
     /**
      * Returns whether this certificate has been installed as a User CA.
-     *
-     * @hide
      */
     public boolean hasCaCertInstalled(byte[] certBuffer) {
-        TrustedCertificateStore certStore = new TrustedCertificateStore();
-        String alias;
-        byte[] pemCert;
         try {
-            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
-                            new ByteArrayInputStream(certBuffer));
-            return certStore.getCertificateAlias(cert) != null;
+            return getCaCertAlias(certBuffer) != null;
         } catch (CertificateException ce) {
             Log.w(TAG, "Could not parse certificate", ce);
         }
@@ -1487,6 +1557,17 @@
     }
 
     /**
+     * Returns the alias of a given CA certificate in the certificate store, or null if it
+     * doesn't exist.
+     */
+    private static String getCaCertAlias(byte[] certBuffer) throws CertificateException {
+        final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        final X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
+                              new ByteArrayInputStream(certBuffer));
+        return new TrustedCertificateStore().getCertificateAlias(cert);
+    }
+
+    /**
      * Called by an application that is administering the device to disable all cameras
      * on the device.  After setting this, no applications will be able to access any cameras
      * on the device.
@@ -1754,6 +1835,25 @@
         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.
+     *
+     * @param packageName The package name of the device owner.
+     */
+    public void clearDeviceOwnerApp(String packageName) {
+        if (mService != null) {
+            try {
+                mService.clearDeviceOwner(packageName);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to clear device owner");
+            }
+        }
+    }
+
     /** @hide */
     public String getDeviceOwner() {
         if (mService != null) {
@@ -1961,17 +2061,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 +2080,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 +2115,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 +2297,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
@@ -2247,15 +2344,20 @@
     }
 
     /**
-     * Sets which components may enter lock task mode.
+     * Sets which packages may enter lock task mode.
      *
-     * This function can only be called by the device owner or the profile owner.
-     * @param components The list of components allowed to enter lock task mode
+     * <p>Any packages that shares uid with an allowed package will also be allowed
+     * to activate lock task.
+     *
+     * This function can only be called by the device owner.
+     * @param packages The list of packages allowed to enter lock task mode
+     *
+     * @see Activity#startLockTask()
      */
-    public void setLockTaskComponents(ComponentName[] components) throws SecurityException {
+    public void setLockTaskPackages(String[] packages) throws SecurityException {
         if (mService != null) {
             try {
-                mService.setLockTaskComponents(components);
+                mService.setLockTaskPackages(packages);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2263,13 +2365,13 @@
     }
 
     /**
-     * This function returns the list of components allowed to start the lock task mode.
+     * This function returns the list of packages allowed to start the lock task mode.
      * @hide
      */
-    public ComponentName[] getLockTaskComponents() {
+    public String[] getLockTaskPackages() {
         if (mService != null) {
             try {
-                return mService.getLockTaskComponents();
+                return mService.getLockTaskPackages();
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2280,12 +2382,12 @@
     /**
      * This function lets the caller know whether the given component is allowed to start the
      * lock task mode.
-     * @param component The component to check
+     * @param pkg The package to check
      */
-    public boolean isLockTaskPermitted(ComponentName component) {
+    public boolean isLockTaskPermitted(String pkg) {
         if (mService != null) {
             try {
-                return mService.isLockTaskPermitted(component);
+                return mService.isLockTaskPermitted(pkg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2330,4 +2432,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..5333ea6 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,14 +108,15 @@
     boolean isDeviceOwner(String packageName);
     String getDeviceOwner();
     String getDeviceOwnerName();
+    void clearDeviceOwner(String packageName);
 
     boolean setProfileOwner(String packageName, String ownerName, int userHandle);
     String getProfileOwner(int userHandle);
     String getProfileOwnerName(int userHandle);
     void setProfileEnabled(in ComponentName who);
 
-    boolean installCaCert(in byte[] certBuffer);
-    void uninstallCaCert(in byte[] certBuffer);
+    boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
+    void uninstallCaCert(in ComponentName admin, in String alias);
 
     void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
     void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
@@ -121,27 +124,33 @@
     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();
 
-    void setLockTaskComponents(in ComponentName[] components);
-    ComponentName[] getLockTaskComponents();
-    boolean isLockTaskPermitted(in ComponentName component);
+    void setLockTaskPackages(in String[] packages);
+    String[] getLockTaskPackages();
+    boolean isLockTaskPermitted(in String pkg);
 
     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);
+
+    void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId);
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
new file mode 100644
index 0000000..4631323
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,522 @@
+/*
+ * 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 {
+    // Zero return always means things are okay.  If returned from
+    // getNextFullRestoreDataChunk(), it means that no data could be delivered at
+    // this time, but the restore is still running and the caller should simply
+    // retry.
+    public static final int TRANSPORT_OK = 0;
+
+    // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
+    // we've delivered the entire data stream for the current restore target.
+    public static final int NO_MORE_DATA = -1;
+
+    // Result codes that indicate real errors are negative and not -1
+    public static final int TRANSPORT_ERROR = -1000;
+    public static final int TRANSPORT_NOT_INITIALIZED = -1001;
+    public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
+    public static final int AGENT_ERROR = -1003;
+    public static final int AGENT_UNKNOWN = -1004;
+
+    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, plus
+     * a description of the structure of the restored archive: either TYPE_KEY_VALUE for
+     * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
+     *
+     * <p>If the package name in the returned RestoreDescription object is the singleton
+     * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
+     * in the current restore session: all packages described in startRestore() have been
+     * processed.
+     *
+     * <p>If this method returns {@code null}, it means that a transport-level error has
+     * occurred and the entire restore operation should be abandoned.
+     *
+     * @return A RestoreDescription object containing the name of one of the packages
+     *   supplied to {@link #startRestore} plus an indicator of the data type of that
+     *   restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
+     *   no more packages can be restored in this session; or {@code null} to indicate
+     *   a transport-level error.
+     */
+    public RestoreDescription nextRestorePackage() {
+        return null;
+    }
+
+    /**
+     * Get the data for the application returned by {@link #nextRestorePackage}, if that
+     * method reported {@link RestoreDescription#TYPE_KEY_VALUE} as its delivery type.
+     * If the package has only TYPE_FULL_STREAM data, then this method will return an
+     * error.
+     *
+     * @param data An open, writable file into which the key/value 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;
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Full restore interfaces
+
+    /**
+     * Ask the transport to provide data for the "current" package being restored.  This
+     * is the package that was just reported by {@link #nextRestorePackage()} as having
+     * {@link RestoreDescription#TYPE_FULL_STREAM} data.
+     *
+     * The transport writes some data to the socket supplied to this call, and returns
+     * the number of bytes written.  The system will then read that many bytes and
+     * stream them to the application's agent for restore, then will call this method again
+     * to receive the next chunk of the archive.  This sequence will be repeated until the
+     * transport returns zero indicating that all of the package's data has been delivered
+     * (or returns a negative value indicating some sort of hard error condition at the
+     * transport level).
+     *
+     * <p>After this method returns zero, the system will then call
+     * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+     * application, and the sequence begins again.
+     *
+     * <p>The transport should always close this socket when returning from this method.
+     * Do not cache this socket across multiple calls or you may leak file descriptors.
+     *
+     * @param socket The file descriptor that the transport will use for delivering the
+     *    streamed archive.  The transport must close this socket in all cases when returning
+     *    from this method.
+     * @return {@link #NO_MORE_DATA} when no more data for the current package is available.
+     *    A positive value indicates the presence of that many bytes to be delivered to the app.
+     *    A value of zero indicates that no data was deliverable at this time, but the restore
+     *    is still running and the caller should retry.  {@link #TRANSPORT_PACKAGE_REJECTED}
+     *    means that the current package's restore operation should be aborted, but that
+     *    the transport itself is still in a good state and so a multiple-package restore
+     *    sequence can still be continued.  Any other negative return value is treated as a
+     *    fatal error condition that aborts all further restore operations on the current dataset.
+     */
+    public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
+        return 0;
+    }
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    public int abortFullRestore() {
+        return BackupTransport.TRANSPORT_OK;
+    }
+
+    /**
+     * 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 RestoreDescription 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();
+        }
+
+        @Override
+        public long requestFullBackupTime() throws RemoteException {
+            return BackupTransport.this.requestFullBackupTime();
+        }
+
+        @Override
+        public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException {
+            return BackupTransport.this.performFullBackup(targetPackage, socket);
+        }
+
+        @Override
+        public int sendBackupData(int numBytes) throws RemoteException {
+            return BackupTransport.this.sendBackupData(numBytes);
+        }
+
+        @Override
+        public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
+            return BackupTransport.this.getNextFullRestoreDataChunk(socket);
+        }
+
+        @Override
+        public int abortFullRestore() {
+            return BackupTransport.this.abortFullRestore();
+        }
+    }
+}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index c629a2e..72bc4f0 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -168,7 +168,15 @@
      */
     void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
-            in String[] packageNames);
+            boolean doCompress, in String[] packageNames);
+
+    /**
+     * Perform a full-dataset backup of the given applications via the currently active
+     * transport.
+     *
+     * @param packageNames The package names of the apps whose data are to be backed up.
+     */
+    void fullTransportBackup(in String[] packageNames);
 
     /**
      * Restore device content from the data stream passed through the given socket.  The
diff --git a/core/java/android/app/backup/RestoreDescription.aidl b/core/java/android/app/backup/RestoreDescription.aidl
new file mode 100644
index 0000000..9cbea78
--- /dev/null
+++ b/core/java/android/app/backup/RestoreDescription.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable RestoreDescription;
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
new file mode 100644
index 0000000..0fb4355
--- /dev/null
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -0,0 +1,101 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Description of the available restore data for a given package.  Returned by a
+ * BackupTransport in response to a request about the next available restorable
+ * package.
+ *
+ * @see BackupTransport#nextRestorePackage()
+ *
+ * @hide
+ */
+public class RestoreDescription implements Parcelable {
+    private final String mPackageName;
+    private final int mDataType;
+
+    private static final String NO_MORE_PACKAGES_SENTINEL = "";
+
+    /**
+     * Return this constant RestoreDescription from BackupTransport.nextRestorePackage()
+     * to indicate that no more package data is available in the current restore operation.
+     */
+    public static final RestoreDescription NO_MORE_PACKAGES =
+            new RestoreDescription(NO_MORE_PACKAGES_SENTINEL, 0);
+
+    // ---------------------------------------
+    // Data type identifiers
+
+    /** This package's restore data is an original-style key/value dataset */
+    public static final int TYPE_KEY_VALUE = 1;
+
+    /** This package's restore data is a tarball-type full data stream */
+    public static final int TYPE_FULL_STREAM = 2;
+
+    // ---------------------------------------
+    // API
+
+    public RestoreDescription(String packageName, int dataType) {
+        mPackageName = packageName;
+        mDataType = dataType;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public int getDataType() {
+        return mDataType;
+    }
+
+    // ---------------------------------------
+    // Parcelable implementation - not used by transport
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mPackageName);
+        out.writeInt(mDataType);
+    }
+
+    public static final Parcelable.Creator<RestoreDescription> CREATOR
+            = new Parcelable.Creator<RestoreDescription>() {
+        public RestoreDescription createFromParcel(Parcel in) {
+            final RestoreDescription unparceled = new RestoreDescription(in);
+            return (NO_MORE_PACKAGES_SENTINEL.equals(unparceled.mPackageName))
+                    ? NO_MORE_PACKAGES
+                    : unparceled;
+        }
+
+        public RestoreDescription[] newArray(int size) {
+            return new RestoreDescription[size];
+        }
+    };
+
+    private RestoreDescription(Parcel in) {
+        mPackageName = in.readString();
+        mDataType = in.readInt();
+    }
+}
diff --git a/core/java/android/app/maintenance/IIdleCallback.aidl b/core/java/android/app/maintenance/IIdleCallback.aidl
deleted file mode 100644
index 582dede..0000000
--- a/core/java/android/app/maintenance/IIdleCallback.aidl
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * 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.
- */
-
-package android.app.maintenance;
-
-import android.app.maintenance.IIdleService;
-
-/**
- * The server side of the idle maintenance IPC protocols.  The app-side implementation
- * invokes on this interface to indicate completion of the (asynchronous) instructions
- * issued by the server.
- *
- * In all cases, the 'who' parameter is the caller's service binder, used to track
- * which idle service instance is reporting.
- *
- * {@hide}
- */
-interface IIdleCallback {
-    /**
-     * Acknowledge receipt and processing of the asynchronous "start idle work" incall.
-     * 'result' is true if the app wants some time to perform ongoing background
-     * idle-time work; or false if the app declares that it does not need any time
-     * for such work.
-     */
-    void acknowledgeStart(int token, boolean result);
-
-    /**
-     * Acknowledge receipt and processing of the asynchronous "stop idle work" incall.
-     */
-    void acknowledgeStop(int token);
-
-    /*
-     * Tell the idle service manager that we're done with our idle maintenance, so that
-     * it can go on to the next one and stop attributing wakelock time to us etc.
-     *
-     * @param opToken The identifier passed in the startIdleMaintenance() call that
-     *        indicated the beginning of this service's idle timeslice.
-     */
-    void idleFinished(int token);
-}
diff --git a/core/java/android/app/maintenance/IIdleService.aidl b/core/java/android/app/maintenance/IIdleService.aidl
deleted file mode 100644
index 54abccd..0000000
--- a/core/java/android/app/maintenance/IIdleService.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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.
- */
-
-package android.app.maintenance;
-
-import android.app.maintenance.IIdleCallback;
-
-/**
- * Interface that the framework uses to communicate with application code
- * that implements an idle-time "maintenance" service.  End user code does
- * not implement this interface directly; instead, the app's idle service
- * implementation will extend android.app.maintenance.IdleService.
- * {@hide}
- */
-oneway interface IIdleService {
-    /**
-     * Begin your idle-time work.
-     */
-    void startIdleMaintenance(IIdleCallback callbackBinder, int token);
-    void stopIdleMaintenance(IIdleCallback callbackBinder, int token);
-}
diff --git a/core/java/android/app/maintenance/IdleService.java b/core/java/android/app/maintenance/IdleService.java
deleted file mode 100644
index 2331b81..0000000
--- a/core/java/android/app/maintenance/IdleService.java
+++ /dev/null
@@ -1,228 +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 android.app.maintenance;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-
-/**
- * Idle maintenance API.  Full docs TBW (to be written).
- */
-public abstract class IdleService extends Service {
-    private static final String TAG = "IdleService";
-
-    static final int MSG_START = 1;
-    static final int MSG_STOP = 2;
-    static final int MSG_FINISH = 3;
-
-    IdleHandler mHandler;
-    IIdleCallback mCallbackBinder;
-    int mToken;
-    final Object mHandlerLock = new Object();
-
-    void ensureHandler() {
-        synchronized (mHandlerLock) {
-            if (mHandler == null) {
-                mHandler = new IdleHandler(getMainLooper());
-            }
-        }
-    }
-
-    /**
-     * TBW: the idle service should supply an intent-filter handling this intent
-     * <p>
-     * <p class="note">The application must also protect the idle service with the
-     * {@code "android.permission.BIND_IDLE_SERVICE"} permission to ensure that other
-     * applications cannot maliciously bind to it.  If an idle service's manifest
-     * declaration does not require that permission, it will never be invoked.
-     * </p>
-     */
-    @SdkConstant(SdkConstantType.SERVICE_ACTION)
-    public static final String SERVICE_INTERFACE =
-            "android.service.idle.IdleService";
-
-    /**
-     * Idle services must be protected with this permission:
-     *
-     * <pre class="prettyprint">
-     *     <service android:name="MyIdleService"
-     *              android:permission="android.permission.BIND_IDLE_SERVICE" >
-     *         ...
-     *     </service>
-     * </pre>
-     *
-     * <p>If an idle service is declared in the manifest but not protected with this
-     * permission, that service will be ignored by the OS.
-     */
-    public static final String PERMISSION_BIND =
-            "android.permission.BIND_IDLE_SERVICE";
-
-    // Trampoline: the callbacks are always run on the main thread
-    IIdleService mBinder = new IIdleService.Stub() {
-        @Override
-        public void startIdleMaintenance(IIdleCallback callbackBinder, int token)
-                throws RemoteException {
-            ensureHandler();
-            Message msg = mHandler.obtainMessage(MSG_START, token, 0, callbackBinder);
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void stopIdleMaintenance(IIdleCallback callbackBinder, int token)
-                throws RemoteException {
-            ensureHandler();
-            Message msg = mHandler.obtainMessage(MSG_STOP, token, 0, callbackBinder);
-            mHandler.sendMessage(msg);
-        }
-    };
-
-    /**
-     * Your application may begin doing "idle" maintenance work in the background.
-     * <p>
-     * Your application may continue to run in the background until it receives a call
-     * to {@link #onIdleStop()}, at which point you <i>must</i> cease doing work.  The
-     * OS will hold a wakelock on your application's behalf from the time this method is
-     * called until after the following call to {@link #onIdleStop()} returns.
-     * </p>
-     * <p>
-     * Returning {@code false} from this method indicates that you have no ongoing work
-     * to do at present.  The OS will respond by immediately calling {@link #onIdleStop()}
-     * and returning your application to its normal stopped state.  Returning {@code true}
-     * indicates that the application is indeed performing ongoing work, so the OS will
-     * let your application run in this state until it's no longer appropriate.
-     * </p>
-     * <p>
-     * You will always receive a matching call to {@link #onIdleStop()} even if your
-     * application returns {@code false} from this method.
-     *
-     * @return {@code true} to indicate that the application wishes to perform some ongoing
-     *     background work; {@code false} to indicate that it does not need to perform such
-     *     work at present.
-     */
-    public abstract boolean onIdleStart();
-
-    /**
-     * Your app's maintenance opportunity is over.  Once the application returns from
-     * this method, the wakelock held by the OS on its behalf will be released.
-     */
-    public abstract void onIdleStop();
-
-    /**
-     * Tell the OS that you have finished your idle work.  Calling this more than once,
-     * or calling it when you have not received an {@link #onIdleStart()} callback, is
-     * an error.
-     *
-     * <p>It is safe to call {@link #finishIdle()} from any thread.
-     */
-    public final void finishIdle() {
-        ensureHandler();
-        mHandler.sendEmptyMessage(MSG_FINISH);
-    }
-
-    class IdleHandler extends Handler {
-        IdleHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START: {
-                    // Call the concrete onIdleStart(), reporting its return value back to
-                    // the OS.  If onIdleStart() throws, report it as a 'false' return but
-                    // rethrow the exception at the offending app.
-                    boolean result = false;
-                    IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
-                    mCallbackBinder = callbackBinder;
-                    final int token = mToken = msg.arg1;
-                    try {
-                        result = IdleService.this.onIdleStart();
-                    } catch (Exception e) {
-                        Log.e(TAG, "Unable to start idle workload", e);
-                        throw new RuntimeException(e);
-                    } finally {
-                        // don't bother if the service already called finishIdle()
-                        if (mCallbackBinder != null) {
-                            try {
-                                callbackBinder.acknowledgeStart(token, result);
-                            } catch (RemoteException re) {
-                                Log.e(TAG, "System unreachable to start idle workload");
-                            }
-                        }
-                    }
-                    break;
-                }
-
-                case MSG_STOP: {
-                    // Structured just like MSG_START for the stop-idle bookend call.
-                    IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
-                    final int token = msg.arg1;
-                    try {
-                        IdleService.this.onIdleStop();
-                    } catch (Exception e) {
-                        Log.e(TAG, "Unable to stop idle workload", e);
-                        throw new RuntimeException(e);
-                    } finally {
-                        if (mCallbackBinder != null) {
-                            try {
-                                callbackBinder.acknowledgeStop(token);
-                            } catch (RemoteException re) {
-                                Log.e(TAG, "System unreachable to stop idle workload");
-                            }
-                        }
-                    }
-                    break;
-                }
-
-                case MSG_FINISH: {
-                    if (mCallbackBinder != null) {
-                        try {
-                            mCallbackBinder.idleFinished(mToken);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "System unreachable to finish idling");
-                        } finally {
-                            mCallbackBinder = null;
-                        }
-                    } else {
-                        Log.e(TAG, "finishIdle() called but the idle service is not started");
-                    }
-                    break;
-                }
-
-                default: {
-                    Slog.w(TAG, "Unknown message " + msg.what);
-                }
-            }
-        }
-    }
-
-    /** @hide */
-    @Override
-    public final IBinder onBind(Intent intent) {
-        return mBinder.asBinder();
-    }
-
-}
diff --git a/core/java/android/app/maintenance/package.html b/core/java/android/app/maintenance/package.html
deleted file mode 100644
index 1c9bf9d..0000000
--- a/core/java/android/app/maintenance/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
-    {@hide}
-</body>
-</html>
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index ad4ccbb..6fbf87d 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -26,6 +26,7 @@
 interface ITrustManager {
     void reportUnlockAttempt(boolean successful, int userId);
     void reportEnabledTrustAgentsChanged(int userId);
+    void reportRequireCredentialEntry(int userId);
     void registerTrustListener(in ITrustListener trustListener);
     void unregisterTrustListener(in ITrustListener trustListener);
 }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index e31c624..6e90590 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -71,6 +71,21 @@
     }
 
     /**
+     * Reports that trust is disabled until credentials have been entered for user {@param userId}.
+     *
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @param userId either an explicit user id or {@link android.os.UserHandle#USER_ALL}
+     */
+    public void reportRequireCredentialEntry(int userId) {
+        try {
+            mService.reportRequireCredentialEntry(userId);
+        } catch (RemoteException e) {
+            onError(e);
+        }
+    }
+
+    /**
      * Registers a listener for trust events.
      *
      * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7b709ac..5175490 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -90,6 +90,11 @@
     public static final String ACTION_PLAYING_STATE_CHANGED =
         "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
 
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
+
     /**
      * A2DP sink device is streaming music. This state can be one of
      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
new file mode 100644
index 0000000..2e27345
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -0,0 +1,432 @@
+/*
+ * 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.bluetooth;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth A2DP Sink
+ * profile.
+ *
+ *<p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothA2dpSink proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothA2dpSink implements BluetoothProfile {
+    private static final String TAG = "BluetoothA2dpSink";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the A2DP Sink
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Playing state of the A2DP Sink
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_PLAYING_STATE_CHANGED =
+        "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED";
+
+    /**
+     * A2DP sink device is streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_PLAYING   =  10;
+
+    /**
+     * A2DP sink device is NOT streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_NOT_PLAYING   =  11;
+
+    /**
+     * Intent used to broadcast the change in the Playing state of the A2DP Sink
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_AUDIO_CONFIG_CHANGED =
+        "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED";
+
+    /**
+     * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent.
+     *
+     * This extra represents the current audio configuration of the A2DP source device.
+     * {@see BluetoothAudioConfig}
+     */
+    public static final String EXTRA_AUDIO_CONFIG
+            = "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private IBluetoothA2dpSink mService;
+    private BluetoothAdapter mAdapter;
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        if (VDBG) Log.d(TAG,"Unbinding service...");
+                        synchronized (mConnection) {
+                            try {
+                                mService = null;
+                                mContext.unbindService(mConnection);
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    } else {
+                        synchronized (mConnection) {
+                            try {
+                                if (mService == null) {
+                                    if (VDBG) Log.d(TAG,"Binding service...");
+                                    doBind();
+                                }
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    }
+                }
+        };
+    /**
+     * Create a BluetoothA2dp proxy object for interacting with the local
+     * Bluetooth A2DP service.
+     *
+     */
+    /*package*/ BluetoothA2dpSink(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothA2dpSink.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    /*package*/ void close() {
+        mServiceListener = null;
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
+        }
+    }
+
+    public void finalize() {
+        close();
+    }
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> Currently, the system supports only 1 connection to the
+     * A2DP profile. The API will automatically disconnect connected
+     * devices before connecting.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            try {
+                return mService.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get the current audio configuration for the A2DP source device,
+     * or null if the device has no audio configuration
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote bluetooth device.
+     * @return audio configuration for the device, or null
+     *
+     * {@see BluetoothAudioConfig}
+     */
+          public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+        if (VDBG) log("getAudioConfig(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getAudioConfig(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return null;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Helper for converting a state to a string.
+     *
+     * For debug use only - strings are not internationalized.
+     * @hide
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+        case STATE_DISCONNECTED:
+            return "disconnected";
+        case STATE_CONNECTING:
+            return "connecting";
+        case STATE_CONNECTED:
+            return "connected";
+        case STATE_DISCONNECTING:
+            return "disconnecting";
+        case STATE_PLAYING:
+            return "playing";
+        case STATE_NOT_PLAYING:
+          return "not playing";
+        default:
+            return "<unknown state " + state + ">";
+        }
+    }
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothA2dpSink.Stub.asInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK,
+                        BluetoothA2dpSink.this);
+            }
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP_SINK);
+            }
+        }
+    };
+
+    private boolean isEnabled() {
+       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+       return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+       if (device == null) return false;
+
+       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+       return false;
+    }
+
+    private static void log(String msg) {
+      Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 42c2aeb..2287246 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2009-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.
@@ -1390,6 +1390,12 @@
         } else if (profile == BluetoothProfile.A2DP) {
             BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.A2DP_SINK) {
+            BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
+            BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
+            return true;
         } else if (profile == BluetoothProfile.INPUT_DEVICE) {
             BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
             return true;
@@ -1402,6 +1408,9 @@
         } else if (profile == BluetoothProfile.MAP) {
             BluetoothMap map = new BluetoothMap(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.HEADSET_CLIENT) {
+            BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -1430,6 +1439,14 @@
                 BluetoothA2dp a2dp = (BluetoothA2dp)proxy;
                 a2dp.close();
                 break;
+            case BluetoothProfile.A2DP_SINK:
+                BluetoothA2dpSink a2dpSink = (BluetoothA2dpSink)proxy;
+                a2dpSink.close();
+                break;
+            case BluetoothProfile.AVRCP_CONTROLLER:
+                BluetoothAvrcpController avrcp = (BluetoothAvrcpController)proxy;
+                avrcp.close();
+                break;
             case BluetoothProfile.INPUT_DEVICE:
                 BluetoothInputDevice iDev = (BluetoothInputDevice)proxy;
                 iDev.close();
@@ -1454,6 +1471,10 @@
                 BluetoothMap map = (BluetoothMap)proxy;
                 map.close();
                 break;
+            case BluetoothProfile.HEADSET_CLIENT:
+                BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient)proxy;
+                headsetClient.close();
+                break;
         }
     }
 
@@ -2058,12 +2079,15 @@
         public void onMultiAdvertiseCallback(int status) {
             // no op
         }
-        /**
-         * Callback reporting LE ATT MTU.
-         * @hide
-         */
+
+        @Override
         public void onConfigureMTU(String address, int mtu, int status) {
             // no op
         }
+
+        @Override
+        public void onConnectionCongested(String address, boolean congested) {
+            // no op
+        }
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothAudioConfig.aidl b/core/java/android/bluetooth/BluetoothAudioConfig.aidl
new file mode 100644
index 0000000..63be5cf
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAudioConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.bluetooth;
+
+parcelable BluetoothAudioConfig;
diff --git a/core/java/android/bluetooth/BluetoothAudioConfig.java b/core/java/android/bluetooth/BluetoothAudioConfig.java
new file mode 100644
index 0000000..03176b9
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAudioConfig.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the audio configuration for a Bluetooth A2DP source device.
+ *
+ * {@see BluetoothA2dpSink}
+ *
+ * {@hide}
+ */
+public final class BluetoothAudioConfig implements Parcelable {
+
+    private final int mSampleRate;
+    private final int mChannelConfig;
+    private final int mAudioFormat;
+
+    public BluetoothAudioConfig(int sampleRate, int channelConfig, int audioFormat) {
+        mSampleRate = sampleRate;
+        mChannelConfig = channelConfig;
+        mAudioFormat = audioFormat;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothAudioConfig) {
+            BluetoothAudioConfig bac = (BluetoothAudioConfig)o;
+            return (bac.mSampleRate == mSampleRate &&
+                    bac.mChannelConfig == mChannelConfig &&
+                    bac.mAudioFormat == mAudioFormat);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mSampleRate | (mChannelConfig << 24) | (mAudioFormat << 28);
+    }
+
+    @Override
+    public String toString() {
+        return "{mSampleRate:" + mSampleRate + ",mChannelConfig:" + mChannelConfig
+                + ",mAudioFormat:" + mAudioFormat + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothAudioConfig> CREATOR =
+            new Parcelable.Creator<BluetoothAudioConfig>() {
+        public BluetoothAudioConfig createFromParcel(Parcel in) {
+            int sampleRate = in.readInt();
+            int channelConfig = in.readInt();
+            int audioFormat = in.readInt();
+            return new BluetoothAudioConfig(sampleRate, channelConfig, audioFormat);
+        }
+        public BluetoothAudioConfig[] newArray(int size) {
+            return new BluetoothAudioConfig[size];
+        }
+    };
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSampleRate);
+        out.writeInt(mChannelConfig);
+        out.writeInt(mAudioFormat);
+    }
+
+    /**
+     * Returns the sample rate in samples per second
+     * @return sample rate
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+
+    /**
+     * Returns the channel configuration (either {@link android.media.AudioFormat#CHANNEL_IN_MONO}
+     * or {@link android.media.AudioFormat#CHANNEL_IN_STEREO})
+     * @return channel configuration
+     */
+    public int getChannelConfig() {
+        return mChannelConfig;
+    }
+
+    /**
+     * Returns the channel audio format (either {@link android.media.AudioFormat#ENCODING_PCM_16BIT}
+     * or {@link android.media.AudioFormat#ENCODING_PCM_8BIT}
+     * @return audio format
+     */
+    public int getAudioFormat() {
+        return mAudioFormat;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothAvrcp.java b/core/java/android/bluetooth/BluetoothAvrcp.java
new file mode 100644
index 0000000..44fe1b7
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcp.java
@@ -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.
+ */
+
+package android.bluetooth;
+
+/**
+ * This class contains constants for Bluetooth AVRCP profile.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcp {
+
+    /*
+     * State flags for Passthrough commands
+    */
+    public static final int PASSTHROUGH_STATE_PRESS    = 0;
+    public static final int PASSTHROUGH_STATE_RELEASE  = 1;
+
+    /*
+     * Operation IDs for Passthrough commands
+    */
+    public static final int PASSTHROUGH_ID_SELECT      = 0x00;    /* select */
+    public static final int PASSTHROUGH_ID_UP          = 0x01;    /* up */
+    public static final int PASSTHROUGH_ID_DOWN        = 0x02;    /* down */
+    public static final int PASSTHROUGH_ID_LEFT        = 0x03;    /* left */
+    public static final int PASSTHROUGH_ID_RIGHT       = 0x04;    /* right */
+    public static final int PASSTHROUGH_ID_RIGHT_UP    = 0x05;    /* right-up */
+    public static final int PASSTHROUGH_ID_RIGHT_DOWN  = 0x06;    /* right-down */
+    public static final int PASSTHROUGH_ID_LEFT_UP     = 0x07;    /* left-up */
+    public static final int PASSTHROUGH_ID_LEFT_DOWN   = 0x08;    /* left-down */
+    public static final int PASSTHROUGH_ID_ROOT_MENU   = 0x09;    /* root menu */
+    public static final int PASSTHROUGH_ID_SETUP_MENU  = 0x0A;    /* setup menu */
+    public static final int PASSTHROUGH_ID_CONT_MENU   = 0x0B;    /* contents menu */
+    public static final int PASSTHROUGH_ID_FAV_MENU    = 0x0C;    /* favorite menu */
+    public static final int PASSTHROUGH_ID_EXIT        = 0x0D;    /* exit */
+    public static final int PASSTHROUGH_ID_0           = 0x20;    /* 0 */
+    public static final int PASSTHROUGH_ID_1           = 0x21;    /* 1 */
+    public static final int PASSTHROUGH_ID_2           = 0x22;    /* 2 */
+    public static final int PASSTHROUGH_ID_3           = 0x23;    /* 3 */
+    public static final int PASSTHROUGH_ID_4           = 0x24;    /* 4 */
+    public static final int PASSTHROUGH_ID_5           = 0x25;    /* 5 */
+    public static final int PASSTHROUGH_ID_6           = 0x26;    /* 6 */
+    public static final int PASSTHROUGH_ID_7           = 0x27;    /* 7 */
+    public static final int PASSTHROUGH_ID_8           = 0x28;    /* 8 */
+    public static final int PASSTHROUGH_ID_9           = 0x29;    /* 9 */
+    public static final int PASSTHROUGH_ID_DOT         = 0x2A;    /* dot */
+    public static final int PASSTHROUGH_ID_ENTER       = 0x2B;    /* enter */
+    public static final int PASSTHROUGH_ID_CLEAR       = 0x2C;    /* clear */
+    public static final int PASSTHROUGH_ID_CHAN_UP     = 0x30;    /* channel up */
+    public static final int PASSTHROUGH_ID_CHAN_DOWN   = 0x31;    /* channel down */
+    public static final int PASSTHROUGH_ID_PREV_CHAN   = 0x32;    /* previous channel */
+    public static final int PASSTHROUGH_ID_SOUND_SEL   = 0x33;    /* sound select */
+    public static final int PASSTHROUGH_ID_INPUT_SEL   = 0x34;    /* input select */
+    public static final int PASSTHROUGH_ID_DISP_INFO   = 0x35;    /* display information */
+    public static final int PASSTHROUGH_ID_HELP        = 0x36;    /* help */
+    public static final int PASSTHROUGH_ID_PAGE_UP     = 0x37;    /* page up */
+    public static final int PASSTHROUGH_ID_PAGE_DOWN   = 0x38;    /* page down */
+    public static final int PASSTHROUGH_ID_POWER       = 0x40;    /* power */
+    public static final int PASSTHROUGH_ID_VOL_UP      = 0x41;    /* volume up */
+    public static final int PASSTHROUGH_ID_VOL_DOWN    = 0x42;    /* volume down */
+    public static final int PASSTHROUGH_ID_MUTE        = 0x43;    /* mute */
+    public static final int PASSTHROUGH_ID_PLAY        = 0x44;    /* play */
+    public static final int PASSTHROUGH_ID_STOP        = 0x45;    /* stop */
+    public static final int PASSTHROUGH_ID_PAUSE       = 0x46;    /* pause */
+    public static final int PASSTHROUGH_ID_RECORD      = 0x47;    /* record */
+    public static final int PASSTHROUGH_ID_REWIND      = 0x48;    /* rewind */
+    public static final int PASSTHROUGH_ID_FAST_FOR    = 0x49;    /* fast forward */
+    public static final int PASSTHROUGH_ID_EJECT       = 0x4A;    /* eject */
+    public static final int PASSTHROUGH_ID_FORWARD     = 0x4B;    /* forward */
+    public static final int PASSTHROUGH_ID_BACKWARD    = 0x4C;    /* backward */
+    public static final int PASSTHROUGH_ID_ANGLE       = 0x50;    /* angle */
+    public static final int PASSTHROUGH_ID_SUBPICT     = 0x51;    /* subpicture */
+    public static final int PASSTHROUGH_ID_F1          = 0x71;    /* F1 */
+    public static final int PASSTHROUGH_ID_F2          = 0x72;    /* F2 */
+    public static final int PASSTHROUGH_ID_F3          = 0x73;    /* F3 */
+    public static final int PASSTHROUGH_ID_F4          = 0x74;    /* F4 */
+    public static final int PASSTHROUGH_ID_F5          = 0x75;    /* F5 */
+    public static final int PASSTHROUGH_ID_VENDOR      = 0x7E;    /* vendor unique */
+    public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80;
+}
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
new file mode 100644
index 0000000..b53a8fc
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -0,0 +1,260 @@
+/*
+ * 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.bluetooth;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth AVRCP Controller
+ * profile.
+ *
+ *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothAvrcpController proxy object.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcpController implements BluetoothProfile {
+    private static final String TAG = "BluetoothAvrcpController";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the AVRCP Controller
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.acrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private IBluetoothAvrcpController mService;
+    private BluetoothAdapter mAdapter;
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        if (VDBG) Log.d(TAG,"Unbinding service...");
+                        synchronized (mConnection) {
+                            try {
+                                mService = null;
+                                mContext.unbindService(mConnection);
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    } else {
+                        synchronized (mConnection) {
+                            try {
+                                if (mService == null) {
+                                    if (VDBG) Log.d(TAG,"Binding service...");
+                                    doBind();
+                                }
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    }
+                }
+        };
+
+    /**
+     * Create a BluetoothAvrcpController proxy object for interacting with the local
+     * Bluetooth AVRCP service.
+     *
+     */
+    /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    /*package*/ void close() {
+        mServiceListener = null;
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
+        }
+    }
+
+    public void finalize() {
+        close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
+        if (DBG) Log.d(TAG, "sendPassThroughCmd");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.sendPassThroughCmd(device, keyCode, keyState);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in sendPassThroughCmd()", e);
+                return;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+    }
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothAvrcpController.Stub.asInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
+                        BluetoothAvrcpController.this);
+            }
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
+            }
+        }
+    };
+
+    private boolean isEnabled() {
+       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+       return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+       if (device == null) return false;
+
+       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+       return false;
+    }
+
+    private static void log(String msg) {
+      Log.d(TAG, msg);
+    }
+}
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/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c9df9c0..6c1a45e 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -85,6 +85,11 @@
     /** A write operation exceeds the maximum length of the attribute */
     public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
 
+    /** A remote device connection is congested.
+     * @hide
+     */
+    public static final int GATT_CONNECTION_CONGESTED = 0x8f;
+
     /** A GATT operation failed, errors other than the above */
     public static final int GATT_FAILURE = 0x101;
 
@@ -267,7 +272,7 @@
                 if (service == null) return;
 
                 BluetoothGattCharacteristic characteristic = service.getCharacteristic(
-                    charUuid.getUuid());
+                    charUuid.getUuid(), charInstId);
                 if (characteristic == null) return;
 
                 characteristic.addDescriptor(new BluetoothGattDescriptor(
@@ -607,6 +612,21 @@
                     Log.w(TAG, "Unhandled exception in callback", ex);
                 }
             }
+
+            /**
+             * Callback indicating the remote device connection is congested.
+             * @hide
+             */
+            public void onConnectionCongested(String address, boolean congested) {
+                if (DBG) Log.d(TAG, "onConnectionCongested() - Device=" + address
+                        + " congested=" + congested);
+                if (!address.equals(mDevice.getAddress())) return;
+                try {
+                    mCallback.onConnectionCongested(BluetoothGatt.this, congested);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception in callback", ex);
+                }
+            }
         };
 
     /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index 5180259..b5e60f2 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -153,4 +153,19 @@
      */
     public void onConfigureMTU(BluetoothGatt gatt, int mtu, int status) {
     }
+
+    /**
+     * Callback indicating that a remote device connection congestestion status has changed.
+     *
+     * An application should refrain from sending additional data to a remote device when
+     * a callback is received with the congested flag set to true. Once the congestion status
+     * is cleared up, the application will receive an additional callback with the congested
+     * flag set to false.
+     *
+     * @param gatt The GATT client associated with the remote device
+     * @param congested true, if the connection is currently congested
+     * @hide
+     */
+    public void onConnectionCongested(BluetoothGatt gatt, boolean congested) {
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 34e8605..2e993c9d 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -265,6 +265,42 @@
                     Log.w(TAG, "Unhandled exception in callback", ex);
                 }
             }
+
+            /**
+             * A notification/indication has been sent.
+             * @hide
+             */
+            public void onNotificationSent(String address, int status) {
+                if (DBG) Log.d(TAG, "onNotificationSent() - "
+                    + "device=" + address + ", status=" + status);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onNotificationSent(device, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Callback indicating the remote device connection is congested.
+             * @hide
+             */
+            public void onConnectionCongested(String address, boolean congested) {
+                if (DBG) Log.d(TAG, "onConnectionCongested() - Device=" + address
+                        + " congested=" + congested);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onConnectionCongested(device, congested);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception in callback", ex);
+                }
+            }
         };
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index fc3ffe8..4fbeb46 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -131,4 +131,34 @@
      */
     public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
     }
+
+    /**
+     * Callback invoked when a notification or indication has been sent to
+     * a remote device.
+     *
+     * <p>When multiple notifications are to be sent, an application must
+     * wait for this callback to be received before sending additional
+     * notifications.
+     *
+     * @param device The remote device the notification has been sent to
+     * @param status 0 if the operation was successful
+     * @hide
+     */
+    public void onNotificationSent(BluetoothDevice device, int status) {
+    }
+
+    /**
+     * Callback indicating that a remote device connection congestestion status has changed.
+     *
+     * An application should refrain from sending additional data (notifications, indications
+     * etc.) to a remote device when a callback is received with the congested flag set
+     * to true. Once the congestion status is cleared up, the application will receive an
+     * additional callback with the congested flag set to false.
+     *
+     * @param device The remote device that triggered the congestion state change
+     * @param congested true, if the connection is currently congested
+     * @hide
+     */
+    public void onConnectionCongested(BluetoothDevice device, boolean congested) {
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
new file mode 100644
index 0000000..ff4ebee
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -0,0 +1,1167 @@
+/*
+ * 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.bluetooth;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API to control Hands Free Profile (HFP role only).
+ * <p>
+ * This class defines methods that shall be used by application to manage profile
+ * connection, calls states and calls actions.
+ * <p>
+ *
+ * @hide
+ * */
+public final class BluetoothHeadsetClient implements BluetoothProfile {
+    private static final String TAG = "BluetoothHeadsetClient";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent sent whenever connection to remote changes.
+     *
+     * <p>It includes two extras:
+     * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code>
+     * and <code>BluetoothProfile.EXTRA_STATE</code>, which
+     * are mandatory.
+     * <p>There are also non mandatory feature extras:
+     * {@link #EXTRA_AG_FEATURE_3WAY_CALLING},
+     * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION},
+     * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT},
+     * {@link #EXTRA_AG_FEATURE_REJECT_CALL},
+     * {@link #EXTRA_AG_FEATURE_ECC},
+     * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD},
+     * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL},
+     * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL},
+     * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT},
+     * {@link #EXTRA_AG_FEATURE_MERGE},
+     * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH},
+     * sent as boolean values only when <code>EXTRA_STATE</code>
+     * is set to <code>STATE_CONNECTED</code>.</p>
+     *
+     * <p>Note that features supported by AG are being sent as
+     * booleans with value <code>true</code>,
+     * and not supported ones are <strong>not</strong> being sent at all.</p>
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent sent whenever audio state changes.
+     *
+     * <p>It includes two mandatory extras:
+     * {@link BluetoothProfile.EXTRA_STATE},
+     * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE},
+     * with possible values:
+     * {@link #STATE_AUDIO_CONNECTING},
+     * {@link #STATE_AUDIO_CONNECTED},
+     * {@link #STATE_AUDIO_DISCONNECTED}</p>
+     * <p>When <code>EXTRA_STATE</code> is set
+     * to </code>STATE_AUDIO_CONNECTED</code>,
+     * it also includes {@link #EXTRA_AUDIO_WBS}
+     * indicating wide band speech support.</p>
+     */
+    public static final String ACTION_AUDIO_STATE_CHANGED =
+        "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
+
+    /**
+     * Intent sending updates of the Audio Gateway state.
+     * Each extra is being sent only when value it
+     * represents has been changed recently on AG.
+     * <p>It can contain one or more of the following extras:
+     * {@link #EXTRA_NETWORK_STATUS},
+     * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH},
+     * {@link #EXTRA_NETWORK_ROAMING},
+     * {@link #EXTRA_BATTERY_LEVEL},
+     * {@link #EXTRA_OPERATOR_NAME},
+     * {@link #EXTRA_VOICE_RECOGNITION},
+     * {@link #EXTRA_IN_BAND_RING}</p>
+     */
+    public static final String ACTION_AG_EVENT =
+            "android.bluetooth.headsetclient.profile.action.AG_EVENT";
+
+    /**
+     * Intent sent whenever state of a call changes.
+     *
+     * <p>It includes:
+     * {@link #EXTRA_CALL},
+     * with value of {@link BluetoothHeadsetClientCall} instance,
+     * representing actual call state.</p>
+     */
+    public static final String ACTION_CALL_CHANGED =
+            "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
+
+    /**
+     * Intent that notifies about the result of the last issued action.
+     * Please note that not every action results in explicit action result code being sent.
+     * Instead other notifications about new Audio Gateway state might be sent,
+     * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value
+     * when for example user started voice recognition from HF unit.
+     */
+    public static final String ACTION_RESULT =
+            "android.bluetooth.headsetclient.profile.action.RESULT";
+
+    /**
+     * Intent that notifies about the number attached to the last voice tag
+     * recorded on AG.
+     *
+     * <p>It contains:
+     * {@link #EXTRA_NUMBER},
+     * with a <code>String</code> value representing phone number.</p>
+     */
+    public static final String ACTION_LAST_VTAG =
+            "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
+
+    public static final int STATE_AUDIO_DISCONNECTED = 0;
+    public static final int STATE_AUDIO_CONNECTING = 1;
+    public static final int STATE_AUDIO_CONNECTED = 2;
+
+    /**
+     * Extra with information if connected audio is WBS.
+     * <p>Possible values: <code>true</code>,
+     *                     <code>false</code>.</p>
+     */
+    public static final String EXTRA_AUDIO_WBS =
+            "android.bluetooth.headsetclient.extra.AUDIO_WBS";
+
+    /**
+     * Extra for AG_EVENT indicates network status.
+     * <p>Value: 0 - network unavailable,
+     *           1 - network available </p>
+     */
+    public static final String EXTRA_NETWORK_STATUS =
+            "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
+    /**
+     * Extra for AG_EVENT intent indicates network signal strength.
+     * <p>Value: <code>Integer</code> representing signal strength.</p>
+     */
+    public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
+            "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
+    /**
+     * Extra for AG_EVENT intent indicates roaming state.
+     * <p>Value: 0 - no roaming
+     *           1 - active roaming</p>
+     */
+    public static final String EXTRA_NETWORK_ROAMING =
+            "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
+    /**
+     * Extra for AG_EVENT intent indicates the battery level.
+     * <p>Value: <code>Integer</code> representing signal strength.</p>
+     */
+    public static final String EXTRA_BATTERY_LEVEL =
+            "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
+    /**
+     * Extra for AG_EVENT intent indicates operator name.
+     * <p>Value: <code>String</code> representing operator name.</p>
+     */
+    public static final String EXTRA_OPERATOR_NAME =
+            "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
+    /**
+     * Extra for AG_EVENT intent indicates voice recognition state.
+     * <p>Value:
+     *          0 - voice recognition stopped,
+     *          1 - voice recognition started.</p>
+     */
+    public static final String EXTRA_VOICE_RECOGNITION =
+            "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
+    /**
+     * Extra for AG_EVENT intent indicates in band ring state.
+     * <p>Value:
+     *          0 - in band ring tone not supported, or
+     *          1 - in band ring tone supported.</p>
+     */
+    public static final String EXTRA_IN_BAND_RING =
+            "android.bluetooth.headsetclient.extra.IN_BAND_RING";
+
+    /**
+     * Extra for AG_EVENT intent indicates subscriber info.
+     * <p>Value: <code>String</code> containing subscriber information.</p>
+     */
+    public static final String EXTRA_SUBSCRIBER_INFO =
+            "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
+
+    /**
+     *  Extra for AG_CALL_CHANGED intent indicates the
+     *  {@link BluetoothHeadsetClientCall} object that has changed.
+     */
+    public static final String EXTRA_CALL =
+            "android.bluetooth.headsetclient.extra.CALL";
+
+    /**
+     * Extra for ACTION_LAST_VTAG intent.
+     * <p>Value: <code>String</code> representing phone number
+     * corresponding to last voice tag recorded on AG</p>
+     */
+    public static final String EXTRA_NUMBER =
+            "android.bluetooth.headsetclient.extra.NUMBER";
+
+    /**
+     * Extra for ACTION_RESULT intent that shows the result code of
+     * last issued action.
+     * <p>Possible results:
+     * {@link #ACTION_RESULT_OK},
+     * {@link #ACTION_RESULT_ERROR},
+     * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
+     * {@link #ACTION_RESULT_ERROR_BUSY},
+     * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
+     * {@link #ACTION_RESULT_ERROR_DELAYED},
+     * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
+     * {@link #ACTION_RESULT_ERROR_CME}</p>
+     */
+    public static final String EXTRA_RESULT_CODE =
+            "android.bluetooth.headsetclient.extra.RESULT_CODE";
+
+    /**
+     * Extra for ACTION_RESULT intent that shows the extended result code of
+     * last issued action.
+     * <p>Value: <code>Integer</code> - error code.</p>
+     */
+    public static final String EXTRA_CME_CODE =
+            "android.bluetooth.headsetclient.extra.CME_CODE";
+
+    /* Extras for AG_FEATURES, extras type is boolean */
+    // TODO verify if all of those are actually useful
+    /**
+     * AG feature: three way calling.
+     */
+    public final static String EXTRA_AG_FEATURE_3WAY_CALLING =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
+    /**
+     * AG feature: voice recognition.
+     */
+    public final static String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
+    /**
+     * AG feature: fetching phone number for voice tagging procedure.
+     */
+    public final static String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
+    /**
+     * AG feature: ability to reject incoming call.
+     */
+    public final static String EXTRA_AG_FEATURE_REJECT_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
+    /**
+     * AG feature: enhanced call handling (terminate specific call, private consultation).
+     */
+    public final static String EXTRA_AG_FEATURE_ECC =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
+    /**
+     * AG feature: response and hold.
+     */
+    public final static String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
+    /**
+     * AG call handling feature: accept held or waiting call in three way calling scenarios.
+     */
+    public final static String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
+    /**
+     * AG call handling feature: release held or waiting call in three way calling scenarios.
+     */
+    public final static String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
+    /**
+     * AG call handling feature: release active call and accept held or waiting call in three way
+     * calling scenarios.
+     */
+    public final static String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
+    /**
+     * AG call handling feature: merge two calls, held and active - multi party conference mode.
+     */
+    public final static String EXTRA_AG_FEATURE_MERGE =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
+    /**
+     * AG call handling feature: merge calls and disconnect from multi party
+     * conversation leaving peers connected to each other.
+     * Note that this feature needs to be supported by mobile network operator
+     * as it requires connection and billing transfer.
+     */
+    public final static String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
+
+    /* Action result codes */
+    public final static int ACTION_RESULT_OK = 0;
+    public final static int ACTION_RESULT_ERROR = 1;
+    public final static int ACTION_RESULT_ERROR_NO_CARRIER = 2;
+    public final static int ACTION_RESULT_ERROR_BUSY = 3;
+    public final static int ACTION_RESULT_ERROR_NO_ANSWER = 4;
+    public final static int ACTION_RESULT_ERROR_DELAYED = 5;
+    public final static int ACTION_RESULT_ERROR_BLACKLISTED = 6;
+    public final static int ACTION_RESULT_ERROR_CME = 7;
+
+    /* Detailed CME error codes */
+    public final static int CME_PHONE_FAILURE                           = 0;
+    public final static int CME_NO_CONNECTION_TO_PHONE                  = 1;
+    public final static int CME_OPERATION_NOT_ALLOWED                   = 3;
+    public final static int CME_OPERATION_NOT_SUPPORTED                 = 4;
+    public final static int CME_PHSIM_PIN_REQUIRED                      = 5;
+    public final static int CME_PHFSIM_PIN_REQUIRED                     = 6;
+    public final static int CME_PHFSIM_PUK_REQUIRED                     = 7;
+    public final static int CME_SIM_NOT_INSERTED                        = 10;
+    public final static int CME_SIM_PIN_REQUIRED                        = 11;
+    public final static int CME_SIM_PUK_REQUIRED                        = 12;
+    public final static int CME_SIM_FAILURE                             = 13;
+    public final static int CME_SIM_BUSY                                = 14;
+    public final static int CME_SIM_WRONG                               = 15;
+    public final static int CME_INCORRECT_PASSWORD                      = 16;
+    public final static int CME_SIM_PIN2_REQUIRED                       = 17;
+    public final static int CME_SIM_PUK2_REQUIRED                       = 18;
+    public final static int CME_MEMORY_FULL                             = 20;
+    public final static int CME_INVALID_INDEX                           = 21;
+    public final static int CME_NOT_FOUND                               = 22;
+    public final static int CME_MEMORY_FAILURE                          = 23;
+    public final static int CME_TEXT_STRING_TOO_LONG                    = 24;
+    public final static int CME_INVALID_CHARACTER_IN_TEXT_STRING        = 25;
+    public final static int CME_DIAL_STRING_TOO_LONG                    = 26;
+    public final static int CME_INVALID_CHARACTER_IN_DIAL_STRING        = 27;
+    public final static int CME_NO_NETWORK_SERVICE                      = 30;
+    public final static int CME_NETWORK_TIMEOUT                         = 31;
+    public final static int CME_EMERGENCY_SERVICE_ONLY                  = 32;
+    public final static int CME_NO_SIMULTANOUS_VOIP_CS_CALLS            = 33;
+    public final static int CME_NOT_SUPPORTED_FOR_VOIP                  = 34;
+    public final static int CME_SIP_RESPONSE_CODE                       = 35;
+    public final static int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED    = 40;
+    public final static int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED    = 41;
+    public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED   = 42;
+    public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED   = 43;
+    public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
+    public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
+    public final static int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED  = 46;
+    public final static int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED  = 47;
+    public final static int CME_HIDDEN_KEY_REQUIRED                     = 48;
+    public final static int CME_EAP_NOT_SUPPORTED                       = 49;
+    public final static int CME_INCORRECT_PARAMETERS                    = 50;
+
+    /* Action policy for other calls when accepting call */
+    public static final int CALL_ACCEPT_NONE = 0;
+    public static final int CALL_ACCEPT_HOLD = 1;
+    public static final int CALL_ACCEPT_TERMINATE = 2;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private IBluetoothHeadsetClient mService;
+    private BluetoothAdapter mAdapter;
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                @Override
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        if (VDBG) Log.d(TAG,"Unbinding service...");
+                        synchronized (mConnection) {
+                            try {
+                                mService = null;
+                                mContext.unbindService(mConnection);
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    } else {
+                        synchronized (mConnection) {
+                            try {
+                                if (mService == null) {
+                                    if (VDBG) Log.d(TAG,"Binding service...");
+                                    Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
+                                    doBind();
+                                }
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    }
+                }
+        };
+
+    /**
+     * Create a BluetoothHeadsetClient proxy object.
+     */
+    /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                 android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothHeadsetClient will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
+        }
+        mServiceListener = null;
+    }
+
+    /**
+     * Connects to remote device.
+     *
+     * Currently, the system supports only 1 connection. So, in case of the
+     * second connection, this implementation will disconnect already connected
+     * device automatically and will process the new one.
+     *
+     * @param device    a remote device we want connect to
+     * @return <code>true</code> if command has been issued successfully;
+     *          <code>false</code> otherwise;
+     *          upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *          intent.
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Disconnects remote device
+     *
+     * @param device    a remote device we want disconnect
+     * @return          <code>true</code> if command has been issued successfully;
+     *                  <code>false</code> otherwise;
+     *                  upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *                  intent.
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.disconnect(device);
+            } catch (RemoteException e) {
+              Log.e(TAG, Log.getStackTraceString(new Throwable()));
+              return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Return the list of connected remote devices
+     *
+     * @return list of connected devices; empty list if nothing is connected.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Returns list of remote devices in a particular state
+     *
+     * @param states    collection of states
+     * @return          list of devices that state matches the states listed in
+     *                  <code>states</code>; empty list if nothing matches the
+     *                  <code>states</code>
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Returns state of the <code>device</code>
+     *
+     * @param device    a remote device
+     * @return          the state of connection of the device
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getConnectionState(" + device + ")");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * The device should already be paired.
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF &&
+                    priority != BluetoothProfile.PRIORITY_ON) {
+              return false;
+            }
+            try {
+                return mService.setPriority(device, priority);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     */
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.getPriority(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return PRIORITY_OFF;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return PRIORITY_OFF;
+    }
+
+    /**
+     * Starts voice recognition.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_AG_EVENT}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean startVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("startVoiceRecognition()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.startVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Stops voice recognition.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_AG_EVENT}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean stopVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("stopVoiceRecognition()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.stopVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Returns list of all calls in any state.
+     *
+     * @param device    remote device
+     * @return          list of calls; empty list if none call exists
+     */
+    public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
+        if (DBG) log("getCurrentCalls()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.getCurrentCalls(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Returns list of current values of AG indicators.
+     *
+     * @param device    remote device
+     * @return          bundle of AG  indicators; null if device is not in
+     *                  CONNECTED state
+     */
+    public Bundle getCurrentAgEvents(BluetoothDevice device) {
+        if (DBG) log("getCurrentCalls()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.getCurrentAgEvents(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Accepts a call
+     *
+     * @param device    remote device
+     * @param flag      action policy while accepting a call. Possible values
+     *                   {@link #CALL_ACCEPT_NONE}, {@link #CALL_ACCEPT_HOLD},
+     *                   {@link #CALL_ACCEPT_TERMINATE}
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     */
+    public boolean acceptCall(BluetoothDevice device, int flag) {
+        if (DBG) log("acceptCall()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.acceptCall(device, flag);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Holds a call.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     */
+    public boolean holdCall(BluetoothDevice device) {
+        if (DBG) log("holdCall()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.holdCall(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Rejects a call.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_REJECT_CALL}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean rejectCall(BluetoothDevice device) {
+        if (DBG) log("rejectCall()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.rejectCall(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Terminates a specified call.
+     *
+     * Works only when Extended Call Control is supported by Audio Gateway.
+     *
+     * @param device    remote device
+     * @param index     index of the call to be terminated
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_ECC}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean terminateCall(BluetoothDevice device, int index) {
+        if (DBG) log("terminateCall()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.terminateCall(device, index);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Enters private mode with a specified call.
+     *
+     * Works only when Extended Call Control is supported by Audio Gateway.
+     *
+     * @param device    remote device
+     * @param index     index of the call to connect in private mode
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_ECC}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean enterPrivateMode(BluetoothDevice device, int index) {
+        if (DBG) log("enterPrivateMode()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.enterPrivateMode(device, index);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Performs explicit call transfer.
+     *
+     * That means connect other calls and disconnect.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent.
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean explicitCallTransfer(BluetoothDevice device) {
+        if (DBG) log("explicitCallTransfer()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.explicitCallTransfer(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Redials last number from Audio Gateway.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent in case of success; {@link #ACTION_RESULT} is sent
+     *                   otherwise;
+     */
+    public boolean redial(BluetoothDevice device) {
+        if (DBG) log("redial()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.redial(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Places a call with specified number.
+     *
+     * @param device    remote device
+     * @param number    valid phone number
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent in case of success; {@link #ACTION_RESULT} is sent
+     *                   otherwise;
+     */
+    public boolean dial(BluetoothDevice device, String number) {
+        if (DBG) log("dial()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.dial(device, number);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Places a call to the number under specified memory location.
+     *
+     * @param device    remote device
+     * @param location  valid memory location
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
+     *                   intent in case of success; {@link #ACTION_RESULT} is sent
+     *                   otherwise;
+     */
+    public boolean dialMemory(BluetoothDevice device, int location) {
+        if (DBG) log("dialMemory()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.dialMemory(device, location);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Sends DTMF code.
+     *
+     * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
+     *
+     * @param device    remote device
+     * @param code  ASCII code
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_RESULT} intent;
+     */
+    public boolean sendDTMF(BluetoothDevice device, byte code) {
+        if (DBG) log("sendDTMF()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.sendDTMF(device, code);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get a number corresponding to last voice tag recorded on AG.
+     *
+     * @param device    remote device
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_LAST_VTAG}
+     *                   or {@link #ACTION_RESULT} intent;
+     *
+     * <p>Feature required for successful execution is being reported by:
+     * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}.
+     * This method invocation will fail silently when feature is not supported.</p>
+     */
+    public boolean getLastVoiceTagNumber(BluetoothDevice device) {
+        if (DBG) log("getLastVoiceTagNumber()");
+        if (mService != null && isEnabled() &&
+                isValidDevice(device)) {
+            try {
+                return mService.getLastVoiceTagNumber(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Accept the incoming connection.
+     */
+    public boolean acceptIncomingConnect(BluetoothDevice device) {
+        if (DBG) log("acceptIncomingConnect");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.acceptIncomingConnect(device);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Reject the incoming connection.
+     */
+    public boolean rejectIncomingConnect(BluetoothDevice device) {
+        if (DBG) log("rejectIncomingConnect");
+        if (mService != null) {
+            try {
+                return mService.rejectIncomingConnect(device);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Returns current audio state of Audio Gateway.
+     *
+     * Note: This is an internal function and shouldn't be exposed
+     */
+    public int getAudioState(BluetoothDevice device) {
+        if (VDBG) log("getAudioState");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getAudioState(device);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+    }
+
+    /**
+     * Initiates a connection of audio channel.
+     *
+     * It setup SCO channel with remote connected Handsfree AG device.
+     *
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
+     *                   intent;
+     */
+    public boolean connectAudio() {
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.connectAudio();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Disconnects audio channel.
+     *
+     * It tears down the SCO channel from remote AG device.
+     *
+     * @return          <code>true</code> if command has been issued successfully;
+     *                   <code>false</code> otherwise;
+     *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
+     *                   intent;
+     */
+    public boolean disconnectAudio() {
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.disconnectAudio();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Get Audio Gateway features
+     *
+     * @param device    remote device
+     * @return          bundle of AG features; null if no service or
+     *                  AG not connected
+     */
+    public Bundle getCurrentAgFeatures(BluetoothDevice device) {
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getCurrentAgFeatures(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothHeadsetClient.Stub.asInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT,
+                        BluetoothHeadsetClient.this);
+            }
+        }
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT);
+            }
+        }
+    };
+
+    private boolean isEnabled() {
+       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+       return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+       if (device == null) return false;
+
+       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+       return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl b/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl
new file mode 100644
index 0000000..35f7923
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.aidl
@@ -0,0 +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.bluetooth;
+
+parcelable BluetoothHeadsetClientCall;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
new file mode 100644
index 0000000..a15bd97
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -0,0 +1,218 @@
+/*
+ * 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents a single call, its state and properties.
+ * It implements {@link Parcelable} for inter-process message passing.
+ * @hide
+ */
+public final class BluetoothHeadsetClientCall implements Parcelable {
+
+    /* Call state */
+    /**
+     * Call is active.
+     */
+    public static final int CALL_STATE_ACTIVE = 0;
+    /**
+     * Call is in held state.
+     */
+    public static final int CALL_STATE_HELD = 1;
+    /**
+     * Outgoing call that is being dialed right now.
+     */
+    public static final int CALL_STATE_DIALING = 2;
+    /**
+     * Outgoing call that remote party has already been alerted about.
+     */
+    public static final int CALL_STATE_ALERTING = 3;
+    /**
+     * Incoming call that can be accepted or rejected.
+     */
+    public static final int CALL_STATE_INCOMING = 4;
+    /**
+     * Waiting call state when there is already an active call.
+     */
+    public static final int CALL_STATE_WAITING = 5;
+    /**
+     * Call that has been held by response and hold
+     * (see Bluetooth specification for further references).
+     */
+    public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6;
+    /**
+     * Call that has been already terminated and should not be referenced as a valid call.
+     */
+    public static final int CALL_STATE_TERMINATED = 7;
+
+    private final int mId;
+    private int mState;
+    private String mNumber;
+    private boolean mMultiParty;
+    private final boolean mOutgoing;
+
+    /**
+     * Creates BluetoothHeadsetClientCall instance.
+     */
+    public BluetoothHeadsetClientCall(int id, int state, String number, boolean multiParty,
+            boolean outgoing) {
+        mId = id;
+        mState = state;
+        mNumber = number != null ? number : "";
+        mMultiParty = multiParty;
+        mOutgoing = outgoing;
+    }
+
+    /**
+     * Sets call's state.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param  state    new call state.
+     */
+    public void setState(int state) {
+        mState = state;
+    }
+
+    /**
+     * Sets call's number.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param number    String representing phone number.
+     */
+    public void setNumber(String number) {
+        mNumber = number;
+    }
+
+    /**
+     * Sets this call as multi party call.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param multiParty    if <code>true</code> sets this call as a part
+     *                      of multi party conference.
+     */
+    public void setMultiParty(boolean multiParty) {
+        mMultiParty = multiParty;
+    }
+
+    /**
+     * Gets call's Id.
+     *
+     * @return call id.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Gets call's current state.
+     *
+     * @return state of this particular phone call.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Gets call's number.
+     *
+     * @return string representing phone number.
+     */
+    public String getNumber() {
+        return mNumber;
+    }
+
+    /**
+     * Checks if call is an active call in a conference mode (aka multi party).
+     *
+     * @return <code>true</code> if call is a multi party call,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isMultiParty() {
+        return mMultiParty;
+    }
+
+    /**
+     * Checks if this call is an outgoing call.
+     *
+     * @return <code>true</code> if its outgoing call,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isOutgoing() {
+        return mOutgoing;
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mId: ");
+        builder.append(mId);
+        builder.append(", mState: ");
+        switch (mState) {
+            case CALL_STATE_ACTIVE: builder.append("ACTIVE"); break;
+            case CALL_STATE_HELD: builder.append("HELD"); break;
+            case CALL_STATE_DIALING: builder.append("DIALING"); break;
+            case CALL_STATE_ALERTING: builder.append("ALERTING"); break;
+            case CALL_STATE_INCOMING: builder.append("INCOMING"); break;
+            case CALL_STATE_WAITING: builder.append("WAITING"); break;
+            case CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: builder.append("HELD_BY_RESPONSE_AND_HOLD"); break;
+            case CALL_STATE_TERMINATED: builder.append("TERMINATED"); break;
+            default: builder.append(mState); break;
+        }
+        builder.append(", mNumber: ");
+        builder.append(mNumber);
+        builder.append(", mMultiParty: ");
+        builder.append(mMultiParty);
+        builder.append(", mOutgoing: ");
+        builder.append(mOutgoing);
+        builder.append("}");
+        return builder.toString();
+    }
+
+    /**
+     * {@link Parcelable.Creator} interface implementation.
+     */
+    public static final Parcelable.Creator<BluetoothHeadsetClientCall> CREATOR =
+            new Parcelable.Creator<BluetoothHeadsetClientCall>() {
+                @Override
+                public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
+                    return new BluetoothHeadsetClientCall(in.readInt(), in.readInt(),
+                            in.readString(), in.readInt() == 1, in.readInt() == 1);
+                }
+
+                @Override
+                public BluetoothHeadsetClientCall[] newArray(int size) {
+                    return new BluetoothHeadsetClientCall[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mId);
+        out.writeInt(mState);
+        out.writeString(mNumber);
+        out.writeInt(mMultiParty ? 1 : 0);
+        out.writeInt(mOutgoing ? 1 : 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 554df3e..252e3d2 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -640,7 +640,7 @@
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
      *
      * @param device Remote Bluetooth Device
-     * @param data Data to send
+     * @param report Report to send
      * @return false on immediate error,
      *               true otherwise
      * @hide
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1574090..1367405 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2010-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.
@@ -104,6 +104,24 @@
     public static final int MAP = 9;
 
     /**
+     * A2DP Sink Profile
+     * @hide
+     */
+    public static final int A2DP_SINK = 10;
+
+    /**
+     * AVRCP Controller Profile
+     * @hide
+     */
+    public static final int AVRCP_CONTROLLER = 11;
+
+    /**
+     * Headset Client - HFP HF Role
+     * @hide
+     */
+    public static final int HEADSET_CLIENT = 16;
+
+    /**
      * 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/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
deleted file mode 100644
index f0c8299..0000000
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ /dev/null
@@ -1,418 +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.
- */
-
-package android.bluetooth;
-
-import android.net.BaseNetworkStateTracker;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.DhcpResults;
-import android.net.NetworkCapabilities;
-import android.net.LinkProperties;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkStateTracker;
-import android.net.NetworkUtils;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * This class tracks the data connection associated with Bluetooth
- * reverse tethering. This is a singleton class and an instance will be
- * created by ConnectivityService. BluetoothService will call into this
- * when a reverse tethered connection needs to be activated.
- *
- * @hide
- */
-public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
-    private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
-    private static final String TAG = "BluetoothTethering";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-
-    // Event sent to the mBtdtHandler when DHCP fails so we can tear down the network.
-    private static final int EVENT_NETWORK_FAILED = 1;
-
-    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
-    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
-    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
-    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
-
-    private final Object mLinkPropertiesLock = new Object();
-    private final Object mNetworkInfoLock = new Object();
-
-    private BluetoothPan mBluetoothPan;
-    private static String mRevTetheredIface;
-    /* For sending events to connectivity service handler */
-    private Handler mCsHandler;
-    private static BluetoothTetheringDataTracker sInstance;
-    private BtdtHandler mBtdtHandler;
-    private AtomicReference<AsyncChannel> mAsyncChannel = new AtomicReference<AsyncChannel>(null);
-
-    private BluetoothTetheringDataTracker() {
-        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
-        mLinkProperties = new LinkProperties();
-        mNetworkCapabilities = new NetworkCapabilities();
-
-        mNetworkInfo.setIsAvailable(false);
-        setTeardownRequested(false);
-    }
-
-    public static synchronized BluetoothTetheringDataTracker getInstance() {
-        if (sInstance == null) sInstance = new BluetoothTetheringDataTracker();
-        return sInstance;
-    }
-
-    public Object Clone() throws CloneNotSupportedException {
-        throw new CloneNotSupportedException();
-    }
-
-    public void setTeardownRequested(boolean isRequested) {
-        mTeardownRequested.set(isRequested);
-    }
-
-    public boolean isTeardownRequested() {
-        return mTeardownRequested.get();
-    }
-
-    /**
-     * Begin monitoring connectivity
-     */
-    public void startMonitoring(Context context, Handler target) {
-        if (DBG) Log.d(TAG, "startMonitoring: target: " + target);
-        mContext = context;
-        mCsHandler = target;
-        if (VDBG) Log.d(TAG, "startMonitoring: mCsHandler: " + mCsHandler);
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN);
-        }
-        mBtdtHandler = new BtdtHandler(target.getLooper(), this);
-    }
-
-    private BluetoothProfile.ServiceListener mProfileServiceListener =
-        new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mBluetoothPan = (BluetoothPan) proxy;
-        }
-        public void onServiceDisconnected(int profile) {
-            mBluetoothPan = null;
-        }
-    };
-
-    /**
-     * Disable connectivity to a network
-     * TODO: do away with return value after making MobileDataStateTracker async
-     */
-    public boolean teardown() {
-        mTeardownRequested.set(true);
-        if (mBluetoothPan != null) {
-            for (BluetoothDevice device: mBluetoothPan.getConnectedDevices()) {
-                mBluetoothPan.disconnect(device);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
-        // not implemented
-    }
-
-    /**
-     * Re-enable connectivity to a network after a {@link #teardown()}.
-     */
-    public boolean reconnect() {
-        mTeardownRequested.set(false);
-        //Ignore
-        return true;
-    }
-
-    /**
-     * Turn the wireless radio off for a network.
-     * @param turnOn {@code true} to turn the radio on, {@code false}
-     */
-    public boolean setRadio(boolean turnOn) {
-        return true;
-    }
-
-    /**
-     * @return true - If are we currently tethered with another device.
-     */
-    public synchronized boolean isAvailable() {
-        return mNetworkInfo.isAvailable();
-    }
-
-    /**
-     * Tells the underlying networking system that the caller wants to
-     * begin using the named feature. The interpretation of {@code feature}
-     * is completely up to each networking implementation.
-     * @param feature the name of the feature to be used
-     * @param callingPid the process ID of the process that is issuing this request
-     * @param callingUid the user ID of the process that is issuing this request
-     * @return an integer value representing the outcome of the request.
-     * The interpretation of this value is specific to each networking
-     * implementation+feature combination, except that the value {@code -1}
-     * always indicates failure.
-     * TODO: needs to go away
-     */
-    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
-    }
-
-    /**
-     * Tells the underlying networking system that the caller is finished
-     * using the named feature. The interpretation of {@code feature}
-     * is completely up to each networking implementation.
-     * @param feature the name of the feature that is no longer needed.
-     * @param callingPid the process ID of the process that is issuing this request
-     * @param callingUid the user ID of the process that is issuing this request
-     * @return an integer value representing the outcome of the request.
-     * The interpretation of this value is specific to each networking
-     * implementation+feature combination, except that the value {@code -1}
-     * always indicates failure.
-     * TODO: needs to go away
-     */
-    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
-    }
-
-    @Override
-    public void setUserDataEnable(boolean enabled) {
-        Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
-    }
-
-    @Override
-    public void setPolicyDataEnable(boolean enabled) {
-        Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
-    }
-
-    /**
-     * Check if private DNS route is set for the network
-     */
-    public boolean isPrivateDnsRouteSet() {
-        return mPrivateDnsRouteSet.get();
-    }
-
-    /**
-     * Set a flag indicating private DNS route is set
-     */
-    public void privateDnsRouteSet(boolean enabled) {
-        mPrivateDnsRouteSet.set(enabled);
-    }
-
-    /**
-     * Fetch NetworkInfo for the network
-     */
-    public NetworkInfo getNetworkInfo() {
-        synchronized (mNetworkInfoLock) {
-            return new NetworkInfo(mNetworkInfo);
-        }
-    }
-
-    /**
-     * Fetch LinkProperties for the network
-     */
-    public LinkProperties getLinkProperties() {
-        synchronized (mLinkPropertiesLock) {
-            return new LinkProperties(mLinkProperties);
-        }
-    }
-
-    /**
-     * Fetch default gateway address for the network
-     */
-    public int getDefaultGatewayAddr() {
-        return mDefaultGatewayAddr.get();
-    }
-
-    /**
-     * Check if default route is set
-     */
-    public boolean isDefaultRouteSet() {
-        return mDefaultRouteSet.get();
-    }
-
-    /**
-     * Set a flag indicating default route is set for the network
-     */
-    public void defaultRouteSet(boolean enabled) {
-        mDefaultRouteSet.set(enabled);
-    }
-
-    /**
-     * Return the system properties name associated with the tcp buffer sizes
-     * for this network.
-     */
-    public String getTcpBufferSizesPropName() {
-        return "net.tcp.buffersize.wifi";
-    }
-
-    private static short countPrefixLength(byte [] mask) {
-        short count = 0;
-        for (byte b : mask) {
-            for (int i = 0; i < 8; ++i) {
-                if ((b & (1 << i)) != 0) {
-                    ++count;
-                }
-            }
-        }
-        return count;
-    }
-
-    void startReverseTether(final LinkProperties linkProperties) {
-        if (linkProperties == null || TextUtils.isEmpty(linkProperties.getInterfaceName())) {
-            Log.e(TAG, "attempted to reverse tether with empty interface");
-            return;
-        }
-        synchronized (mLinkPropertiesLock) {
-            if (mLinkProperties.getInterfaceName() != null) {
-                Log.e(TAG, "attempted to reverse tether while already in process");
-                return;
-            }
-            mLinkProperties = linkProperties;
-        }
-        Thread dhcpThread = new Thread(new Runnable() {
-            public void run() {
-                //Currently this thread runs independently.
-                DhcpResults dhcpResults = new DhcpResults();
-                boolean success = NetworkUtils.runDhcp(linkProperties.getInterfaceName(),
-                        dhcpResults);
-                synchronized (mLinkPropertiesLock) {
-                    if (linkProperties.getInterfaceName() != mLinkProperties.getInterfaceName()) {
-                        Log.e(TAG, "obsolete DHCP run aborted");
-                        return;
-                    }
-                    if (!success) {
-                        Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
-                        mBtdtHandler.obtainMessage(EVENT_NETWORK_FAILED).sendToTarget();
-                        return;
-                    }
-                    mLinkProperties = dhcpResults.linkProperties;
-                    synchronized (mNetworkInfoLock) {
-                        mNetworkInfo.setIsAvailable(true);
-                        mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-                        if (mCsHandler != null) {
-                            Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED,
-                                    new NetworkInfo(mNetworkInfo));
-                            msg.sendToTarget();
-                       }
-                    }
-                    return;
-                }
-            }
-        });
-        dhcpThread.start();
-    }
-
-    void stopReverseTether() {
-        synchronized (mLinkPropertiesLock) {
-            if (TextUtils.isEmpty(mLinkProperties.getInterfaceName())) {
-                Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
-                return;
-            }
-            NetworkUtils.stopDhcp(mLinkProperties.getInterfaceName());
-            mLinkProperties.clear();
-            synchronized (mNetworkInfoLock) {
-                mNetworkInfo.setIsAvailable(false);
-                mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
-
-                if (mCsHandler != null) {
-                    mCsHandler.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo)).
-                            sendToTarget();
-                }
-            }
-        }
-    }
-
-    public void setDependencyMet(boolean met) {
-        // not supported on this network
-    }
-
-    @Override
-    public void addStackedLink(LinkProperties link) {
-        mLinkProperties.addStackedLink(link);
-    }
-
-    @Override
-    public void removeStackedLink(LinkProperties link) {
-        mLinkProperties.removeStackedLink(link);
-    }
-
-    static class BtdtHandler extends Handler {
-        private AsyncChannel mStackChannel;
-        private final BluetoothTetheringDataTracker mBtdt;
-
-        BtdtHandler(Looper looper, BluetoothTetheringDataTracker parent) {
-            super(looper);
-            mBtdt = parent;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (VDBG) Log.d(TAG, "got CMD_CHANNEL_HALF_CONNECTED");
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        AsyncChannel ac = (AsyncChannel)msg.obj;
-                        if (mBtdt.mAsyncChannel.compareAndSet(null, ac) == false) {
-                            Log.e(TAG, "Trying to set mAsyncChannel twice!");
-                        } else {
-                            ac.sendMessage(
-                                    AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                        }
-                    }
-                    break;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    if (VDBG) Log.d(TAG, "got CMD_CHANNEL_DISCONNECTED");
-                    mBtdt.stopReverseTether();
-                    mBtdt.mAsyncChannel.set(null);
-                    break;
-                case NetworkStateTracker.EVENT_NETWORK_CONNECTED:
-                    LinkProperties linkProperties = (LinkProperties)(msg.obj);
-                    if (VDBG) Log.d(TAG, "got EVENT_NETWORK_CONNECTED, " + linkProperties);
-                    mBtdt.startReverseTether(linkProperties);
-                    break;
-                case NetworkStateTracker.EVENT_NETWORK_DISCONNECTED:
-                    linkProperties = (LinkProperties)(msg.obj);
-                    if (VDBG) Log.d(TAG, "got EVENT_NETWORK_DISCONNECTED, " + linkProperties);
-                    mBtdt.stopReverseTether();
-                    break;
-                case EVENT_NETWORK_FAILED:
-                    if (VDBG) Log.d(TAG, "got EVENT_NETWORK_FAILED");
-                    mBtdt.teardown();
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void supplyMessenger(Messenger messenger) {
-        if (messenger != null) {
-            new AsyncChannel().connect(mContext, mBtdtHandler, messenger);
-        }
-    }
-}
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/IBluetoothA2dpSink.aidl b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
new file mode 100644
index 0000000..b7c6476
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.bluetooth;
+
+import android.bluetooth.BluetoothAudioConfig;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * APIs for Bluetooth A2DP sink service
+ *
+ * @hide
+ */
+interface IBluetoothA2dpSink {
+    boolean connect(in BluetoothDevice device);
+    boolean disconnect(in BluetoothDevice device);
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
+    BluetoothAudioConfig getAudioConfig(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
new file mode 100644
index 0000000..f917a50
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * APIs for Bluetooth AVRCP controller service
+ *
+ * @hide
+ */
+interface IBluetoothAvrcpController {
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
+    void sendPassThroughCmd(in BluetoothDevice device, int keyCode, int keyState);
+}
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/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index bf9e0a7..946a6f6 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -22,7 +22,7 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-interface IBluetoothGattCallback {
+oneway interface IBluetoothGattCallback {
     void onClientRegistered(in int status, in int clientIf);
     void onClientConnectionState(in int status, in int clientIf,
                                  in boolean connected, in String address);
@@ -63,7 +63,8 @@
                              in int charInstId, in ParcelUuid charUuid,
                              in byte[] value);
     void onReadRemoteRssi(in String address, in int rssi, in int status);
-    oneway void onAdvertiseStateChange(in int advertiseState, in int status);
-    oneway void onMultiAdvertiseCallback(in int status);
+    void onAdvertiseStateChange(in int advertiseState, in int status);
+    void onMultiAdvertiseCallback(in int status);
     void onConfigureMTU(in String address, in int mtu, in int status);
+    void onConnectionCongested(in String address, in boolean congested);
 }
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index ae9bffc..6e31da1 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -22,7 +22,7 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-interface IBluetoothGattServerCallback {
+oneway interface IBluetoothGattServerCallback {
     void onServerRegistered(in int status, in int serverIf);
     void onScanResult(in String address, in int rssi, in byte[] advData);
     void onServerConnectionState(in int status, in int serverIf,
@@ -58,4 +58,6 @@
                                      in ParcelUuid descrId,
                                      in byte[] value);
     void onExecuteWrite(in String address, in int transId, in boolean execWrite);
+    void onNotificationSent(in String address, in int status);
+    void onConnectionCongested(in String address, in boolean congested);
 }
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
new file mode 100644
index 0000000..e518b7d
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -0,0 +1,67 @@
+/*
+ * 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.os.Bundle;
+
+/**
+ * API for Bluetooth Headset Client service (HFP HF Role)
+ *
+ * {@hide}
+ */
+interface IBluetoothHeadsetClient {
+    boolean connect(in BluetoothDevice device);
+    boolean disconnect(in BluetoothDevice device);
+
+    boolean acceptIncomingConnect(in BluetoothDevice device);
+    boolean rejectIncomingConnect(in BluetoothDevice device);
+
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
+    boolean setPriority(in BluetoothDevice device, int priority);
+    int getPriority(in BluetoothDevice device);
+
+    boolean startVoiceRecognition(in BluetoothDevice device);
+    boolean stopVoiceRecognition(in BluetoothDevice device);
+
+    List<BluetoothHeadsetClientCall> getCurrentCalls(in BluetoothDevice device);
+    Bundle getCurrentAgEvents(in BluetoothDevice device);
+
+    boolean acceptCall(in BluetoothDevice device, int flag);
+    boolean holdCall(in BluetoothDevice device);
+    boolean rejectCall(in BluetoothDevice device);
+    boolean terminateCall(in BluetoothDevice device, int index);
+
+    boolean enterPrivateMode(in BluetoothDevice device, int index);
+    boolean explicitCallTransfer(in BluetoothDevice device);
+
+    boolean redial(in BluetoothDevice device);
+    boolean dial(in BluetoothDevice device, String number);
+    boolean dialMemory(in BluetoothDevice device, int location);
+
+    boolean sendDTMF(in BluetoothDevice device, byte code);
+    boolean getLastVoiceTagNumber(in BluetoothDevice device);
+
+    int getAudioState(in BluetoothDevice device);
+    boolean connectAudio();
+    boolean disconnectAudio();
+
+    Bundle getCurrentAgFeatures(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index ed43407..c20b81b 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -355,6 +355,11 @@
         public void onConfigureMTU(String address, int mtu, int status) {
             // no op
         }
+
+        @Override
+        public void onConnectionCongested(String address, boolean congested) {
+            // no op
+        }
     }
 
     private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 4c6346c..fbaf5d2 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -358,6 +358,11 @@
         public void onConfigureMTU(String address, int mtu, int status) {
             // no op
         }
+
+        @Override
+        public void onConnectionCongested(String address, boolean congested) {
+            // no op
+        }
     }
 
     private void postCallbackError(final ScanCallback callback, final int errorCode) {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 392bfbc..be70411 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1751,6 +1751,15 @@
      * @param extras any extras to pass to the SyncAdapter.
      */
     public static void requestSync(Account account, String authority, Bundle extras) {
+        requestSyncAsUser(account, authority, UserHandle.getCallingUserId(), extras);
+    }
+
+    /**
+     * @see #requestSync(Account, String, Bundle)
+     * @hide
+     */
+    public static void requestSyncAsUser(Account account, String authority, int userId,
+            Bundle extras) {
         if (extras == null) {
             throw new IllegalArgumentException("Must specify extras.");
         }
@@ -1760,7 +1769,11 @@
                 .setExtras(extras)
                 .syncOnce()     // Immediate sync.
                 .build();
-        requestSync(request);
+        try {
+            getContentService().syncAsUser(request, userId);
+        } catch(RemoteException e) {
+            // Shouldn't happen.
+        }
     }
 
     /**
@@ -1839,6 +1852,17 @@
     }
 
     /**
+     * @see #cancelSync(Account, String)
+     * @hide
+     */
+    public static void cancelSyncAsUser(Account account, String authority, int userId) {
+        try {
+            getContentService().cancelSyncAsUser(account, authority, null, userId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Get information about the SyncAdapters that are known to the system.
      * @return an array of SyncAdapters that have registered with the system
      */
@@ -1851,6 +1875,18 @@
     }
 
     /**
+     * @see #getSyncAdapterTypes()
+     * @hide
+     */
+    public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        try {
+            return getContentService().getSyncAdapterTypesAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Check if the provider should be synced when a network tickle is received
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
@@ -1868,6 +1904,19 @@
     }
 
     /**
+     * @see #getSyncAutomatically(Account, String)
+     * @hide
+     */
+    public static boolean getSyncAutomaticallyAsUser(Account account, String authority,
+            int userId) {
+        try {
+            return getContentService().getSyncAutomaticallyAsUser(account, authority, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Set whether or not the provider is synced when it receives a network tickle.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2032,6 +2081,18 @@
     }
 
     /**
+     * @see #getIsSyncable(Account, String)
+     * @hide
+     */
+    public static int getIsSyncableAsUser(Account account, String authority, int userId) {
+        try {
+            return getContentService().getIsSyncableAsUser(account, authority, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Set whether this account/provider is syncable.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2063,6 +2124,18 @@
     }
 
     /**
+     * @see #getMasterSyncAutomatically()
+     * @hide
+     */
+    public static boolean getMasterSyncAutomaticallyAsUser(int userId) {
+        try {
+            return getContentService().getMasterSyncAutomaticallyAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Sets the master auto-sync setting that applies to all the providers and accounts.
      * If this is false then the per-provider auto-sync setting is ignored.
      * <p>This method requires the caller to hold the permission
@@ -2147,6 +2220,18 @@
     }
 
     /**
+     * @see #getCurrentSyncs()
+     * @hide
+     */
+    public static List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+        try {
+            return getContentService().getCurrentSyncsAsUser(userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Returns the status that matches the authority.
      * @param account the account whose setting we are querying
      * @param authority the provider whose behavior is being queried
@@ -2162,6 +2247,19 @@
     }
 
     /**
+     * @see #getSyncStatus(Account, String)
+     * @hide
+     */
+    public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+            int userId) {
+        try {
+            return getContentService().getSyncStatusAsUser(account, authority, null, userId);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Return true if the pending status is true of any matching authorities.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#READ_SYNC_STATS}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cdcfd2e..fd19b40 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2035,6 +2035,7 @@
             AUDIO_SERVICE,
             MEDIA_ROUTER_SERVICE,
             TELEPHONY_SERVICE,
+            TELECOMM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
             TEXT_SERVICES_MANAGER_SERVICE,
@@ -2163,6 +2164,8 @@
      * @see android.media.MediaRouter
      * @see #TELEPHONY_SERVICE
      * @see android.telephony.TelephonyManager
+     * @see #TELECOMM_SERVICE
+     * @see android.telecomm.TelecommManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
@@ -2460,7 +2463,6 @@
      *
      * @see #getSystemService
      * @see android.app.FingerprintManager
-     * @hide
      */
     public static final String FINGERPRINT_SERVICE = "fingerprint";
 
@@ -2495,6 +2497,16 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.telecomm.TelecommManager} to manage telecomm-related features
+     * of the device.
+     *
+     * @see #getSystemService
+     * @see android.telecomm.TelecommManager
+     */
+    public static final String TELECOMM_SERVICE = "telecomm";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.text.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
@@ -2686,6 +2698,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/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 373f2fb..1e96713 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -60,7 +60,9 @@
      * Start a sync given a request.
      */
     void sync(in SyncRequest request);
+    void syncAsUser(in SyncRequest request, int userId);
     void cancelSync(in Account account, String authority, in ComponentName cname);
+    void cancelSyncAsUser(in Account account, String authority, in ComponentName cname, int userId);
 
     /** Cancel a sync, providing information about the sync to be cancelled. */
      void cancelRequest(in SyncRequest request);
@@ -71,6 +73,7 @@
      * @return true if the provider should be synced when a network tickle is received
      */
     boolean getSyncAutomatically(in Account account, String providerName);
+    boolean getSyncAutomaticallyAsUser(in Account account, String providerName, int userId);
 
     /**
      * Set whether or not the provider is synced when it receives a network tickle.
@@ -114,6 +117,7 @@
      * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
      */
     int getIsSyncable(in Account account, String providerName);
+    int getIsSyncableAsUser(in Account account, String providerName, int userId);
 
     /**
      * Set whether this account/provider is syncable.
@@ -124,14 +128,17 @@
     void setMasterSyncAutomatically(boolean flag);
 
     boolean getMasterSyncAutomatically();
+    boolean getMasterSyncAutomaticallyAsUser(int userId);
 
     List<SyncInfo> getCurrentSyncs();
+    List<SyncInfo> getCurrentSyncsAsUser(int userId);
 
     /**
      * Returns the types of the SyncAdapters that are registered with the system.
      * @return Returns the types of the SyncAdapters that are registered with the system.
      */
     SyncAdapterType[] getSyncAdapterTypes();
+    SyncAdapterType[] getSyncAdapterTypesAsUser(int userId);
 
     /**
      * Returns true if there is currently a operation for the given account/authority or service
@@ -152,6 +159,8 @@
      * non-null.
      */
     SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname);
+    SyncStatusInfo getSyncStatusAsUser(in Account account, String authority, in ComponentName cname,
+            int userId);
 
     /**
      * Return true if the pending status is true of any matching authorities.
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/Intent.java b/core/java/android/content/Intent.java
index 6e53a6fb..e24dc84 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -6821,72 +6822,12 @@
         if (other == null) {
             return false;
         }
-        if (mAction != other.mAction) {
-            if (mAction != null) {
-                if (!mAction.equals(other.mAction)) {
-                    return false;
-                }
-            } else {
-                if (!other.mAction.equals(mAction)) {
-                    return false;
-                }
-            }
-        }
-        if (mData != other.mData) {
-            if (mData != null) {
-                if (!mData.equals(other.mData)) {
-                    return false;
-                }
-            } else {
-                if (!other.mData.equals(mData)) {
-                    return false;
-                }
-            }
-        }
-        if (mType != other.mType) {
-            if (mType != null) {
-                if (!mType.equals(other.mType)) {
-                    return false;
-                }
-            } else {
-                if (!other.mType.equals(mType)) {
-                    return false;
-                }
-            }
-        }
-        if (mPackage != other.mPackage) {
-            if (mPackage != null) {
-                if (!mPackage.equals(other.mPackage)) {
-                    return false;
-                }
-            } else {
-                if (!other.mPackage.equals(mPackage)) {
-                    return false;
-                }
-            }
-        }
-        if (mComponent != other.mComponent) {
-            if (mComponent != null) {
-                if (!mComponent.equals(other.mComponent)) {
-                    return false;
-                }
-            } else {
-                if (!other.mComponent.equals(mComponent)) {
-                    return false;
-                }
-            }
-        }
-        if (mCategories != other.mCategories) {
-            if (mCategories != null) {
-                if (!mCategories.equals(other.mCategories)) {
-                    return false;
-                }
-            } else {
-                if (!other.mCategories.equals(mCategories)) {
-                    return false;
-                }
-            }
-        }
+        if (!Objects.equals(this.mAction, other.mAction)) return false;
+        if (!Objects.equals(this.mData, other.mData)) return false;
+        if (!Objects.equals(this.mType, other.mType)) return false;
+        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
+        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
+        if (!Objects.equals(this.mCategories, other.mCategories)) return false;
 
         return true;
     }
@@ -7375,6 +7316,7 @@
             for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) {
                 out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx));
             }
+            out.endTag(null, TAG_CATEGORIES);
         }
     }
 
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/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 791e5aa..bcf1e87 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -104,6 +104,28 @@
     public int documentLaunchMode;
 
     /**
+     * Constant corresponding to <code>persistRootOnly</code> in
+     * the {@link android.R.attr#persistableMode} attribute.
+     */
+    public static final int PERSIST_ROOT_ONLY = 0;
+    /**
+     * Constant corresponding to <code>doNotPersist</code> in
+     * the {@link android.R.attr#persistableMode} attribute.
+     */
+    public static final int DO_NOT_PERSIST = 1;
+    /**
+     * Constant corresponding to <code>persistAcrossReboots</code> in
+     * the {@link android.R.attr#persistableMode} attribute.
+     */
+    public static final int PERSIST_ACROSS_REBOOTS = 2;
+    /**
+     * Value indicating how this activity is to be persisted across
+     * reboots for restoring in the Recents list.
+     * {@link android.R.attr#persistableMode}
+     */
+    public int persistableMode;
+
+    /**
      * The maximum number of tasks rooted at this activity that can be in the recent task list.
      * Refer to {@link android.R.attr#maxRecents}.
      */
@@ -230,11 +252,12 @@
      */
     public static final int FLAG_IMMERSIVE = 0x0800;
     /**
-     * Bit in {@link #flags} indicating that this activity is to be persisted across
-     * reboots for display in the Recents list.
-     * {@link android.R.attr#persistable}
+     * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+     * baseIntent replaced by the activity immediately above this. Each activity may further
+     * relinquish its identity to the activity above it using this flag. Set from the
+     * android.R.attr#relinquishTaskIdentity attribute.
      */
-    public static final int FLAG_PERSISTABLE = 0x1000;
+    public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
     /**
      * Bit in {@link #flags} indicating that tasks started with this activity are to be
      * removed from the recent list of tasks when the last activity in the task is finished.
@@ -641,13 +664,23 @@
         return theme != 0 ? theme : applicationInfo.theme;
     }
 
+    private String persistableModeToString() {
+        switch(persistableMode) {
+            case PERSIST_ROOT_ONLY: return "PERSIST_ROOT_ONLY";
+            case DO_NOT_PERSIST: return "DO_NOT_PERSIST";
+            case PERSIST_ACROSS_REBOOTS: return "PERSIST_ACROSS_REBOOTS";
+            default: return "UNKNOWN=" + persistableMode;
+        }
+    }
+
     public void dump(Printer pw, String prefix) {
         super.dumpFront(pw, prefix);
         if (permission != null) {
             pw.println(prefix + "permission=" + permission);
         }
         pw.println(prefix + "taskAffinity=" + taskAffinity
-                + " targetActivity=" + targetActivity);
+                + " targetActivity=" + targetActivity
+                + " persistableMode=" + persistableModeToString());
         if (launchMode != 0 || flags != 0 || theme != 0) {
             pw.println(prefix + "launchMode=" + launchMode
                     + " flags=0x" + Integer.toHexString(flags)
@@ -688,6 +721,7 @@
         dest.writeInt(softInputMode);
         dest.writeInt(uiOptions);
         dest.writeString(parentActivityName);
+        dest.writeInt(persistableMode);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -713,5 +747,6 @@
         softInputMode = source.readInt();
         uiOptions = source.readInt();
         parentActivityName = source.readString();
+        persistableMode = source.readInt();
     }
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 6b44a11..be4e864 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -23,8 +23,12 @@
 import android.os.Parcelable;
 import android.util.Printer;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.text.Collator;
+import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Objects;
 
 /**
  * Information you can retrieve about a particular application.  This
@@ -321,6 +325,15 @@
     public static final int FLAG_IS_GAME = 1<<25;
 
     /**
+     * Value for {@link #flags}: {@code true} if the application asks that only
+     * full-data streaming backups of its data be performed even though it defines
+     * a {@link android.app.backup.BackupAgent BackupAgent}, which normally
+     * indicates that the app will manage its backed-up data via incremental
+     * key/value updates.
+     */
+    public static final int FLAG_FULL_BACKUP_ONLY = 1<<26;
+
+    /**
      * Value for {@link #flags}: set to {@code true} if the application
      * is permitted to hold privileged permissions.
      *
@@ -398,17 +411,30 @@
     public int largestWidthLimitDp = 0;
 
     /**
-     * Full path to the location of this package.
+     * Full path to the base APK for this application.
      */
     public String sourceDir;
 
     /**
-     * Full path to the location of the publicly available parts of this
-     * package (i.e. the primary resource package and manifest).  For
-     * non-forward-locked apps this will be the same as {@link #sourceDir).
+     * Full path to the publicly available parts of {@link #sourceDir},
+     * including resources and manifest. This may be different from
+     * {@link #sourceDir} if an application is forward locked.
      */
     public String publicSourceDir;
-    
+
+    /**
+     * Full paths to zero or more split APKs that, when combined with the base
+     * APK defined in {@link #sourceDir}, form a complete application.
+     */
+    public String[] splitSourceDirs;
+
+    /**
+     * Full path to the publicly available parts of {@link #splitSourceDirs},
+     * including resources and manifest. This may be different from
+     * {@link #splitSourceDirs} if an application is forward locked.
+     */
+    public String[] splitPublicSourceDirs;
+
     /**
      * Full paths to the locations of extra resource packages this application
      * uses. This field is only used if there are extra resource packages,
@@ -512,13 +538,16 @@
                 + " compatibleWidthLimitDp=" + compatibleWidthLimitDp
                 + " largestWidthLimitDp=" + largestWidthLimitDp);
         pw.println(prefix + "sourceDir=" + sourceDir);
-        if (sourceDir == null) {
-            if (publicSourceDir != null) {
-                pw.println(prefix + "publicSourceDir=" + publicSourceDir);
-            }
-        } else if (!sourceDir.equals(publicSourceDir)) {
+        if (!Objects.equals(sourceDir, publicSourceDir)) {
             pw.println(prefix + "publicSourceDir=" + publicSourceDir);
         }
+        if (!ArrayUtils.isEmpty(splitSourceDirs)) {
+            pw.println(prefix + "splitSourceDirs=" + Arrays.toString(splitSourceDirs));
+        }
+        if (!ArrayUtils.isEmpty(splitPublicSourceDirs)
+                && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) {
+            pw.println(prefix + "splitPublicSourceDirs=" + Arrays.toString(splitPublicSourceDirs));
+        }
         if (resourceDirs != null) {
             pw.println(prefix + "resourceDirs=" + resourceDirs);
         }
@@ -591,6 +620,8 @@
         largestWidthLimitDp = orig.largestWidthLimitDp;
         sourceDir = orig.sourceDir;
         publicSourceDir = orig.publicSourceDir;
+        splitSourceDirs = orig.splitSourceDirs;
+        splitPublicSourceDirs = orig.splitPublicSourceDirs;
         nativeLibraryDir = orig.nativeLibraryDir;
         cpuAbi = orig.cpuAbi;
         resourceDirs = orig.resourceDirs;
@@ -633,6 +664,8 @@
         dest.writeInt(largestWidthLimitDp);
         dest.writeString(sourceDir);
         dest.writeString(publicSourceDir);
+        dest.writeStringArray(splitSourceDirs);
+        dest.writeStringArray(splitPublicSourceDirs);
         dest.writeString(nativeLibraryDir);
         dest.writeString(cpuAbi);
         dest.writeStringArray(resourceDirs);
@@ -674,6 +707,8 @@
         largestWidthLimitDp = source.readInt();
         sourceDir = source.readString();
         publicSourceDir = source.readString();
+        splitSourceDirs = source.readStringArray();
+        splitPublicSourceDirs = source.readStringArray();
         nativeLibraryDir = source.readString();
         cpuAbi = source.readString();
         resourceDirs = source.readStringArray();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6cb781f..6111b01 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,13 @@
 
     void clearPackagePersistentPreferredActivities(String packageName, int userId);
 
-    void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest);
+    void addCrossProfileIntentFilter(in IntentFilter intentFilter, int sourceUserId, int targetUserId,
+            int flags);
 
-    void clearForwardingIntentFilters(int userIdOrig);
+    void addCrossProfileIntentsForPackage(in String packageName, int sourceUserId,
+            int targetUserId);
+
+    void clearCrossProfileIntentFilters(int sourceUserId);
 
     /**
      * Report the set of 'Home' activity candidates, plus (if any) which of them
@@ -433,6 +436,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/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index a977e41..dab0caf 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -30,17 +30,32 @@
      * "package" attribute.
      */
     public String targetPackage;
-    
+
     /**
-     * Full path to the location of this package.
+     * Full path to the base APK for this application.
      */
     public String sourceDir;
-    
+
     /**
-     * Full path to the location of the publicly available parts of this package (i.e. the resources
-     * and manifest).  For non-forward-locked apps this will be the same as {@link #sourceDir).
+     * Full path to the publicly available parts of {@link #sourceDir},
+     * including resources and manifest. This may be different from
+     * {@link #sourceDir} if an application is forward locked.
      */
     public String publicSourceDir;
+
+    /**
+     * Full paths to zero or more split APKs that, when combined with the base
+     * APK defined in {@link #sourceDir}, form a complete application.
+     */
+    public String[] splitSourceDirs;
+
+    /**
+     * Full path to the publicly available parts of {@link #splitSourceDirs},
+     * including resources and manifest. This may be different from
+     * {@link #splitSourceDirs} if an application is forward locked.
+     */
+    public String[] splitPublicSourceDirs;
+
     /**
      * Full path to a directory assigned to the package for its persistent
      * data.
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 69fa408..6c10bb8 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -57,6 +57,65 @@
 
     private List<OnAppsChangedListener> mListeners
             = new ArrayList<OnAppsChangedListener>();
+    private List<OnAppsChangedCallback> mCallbacks
+            = new ArrayList<OnAppsChangedCallback>();
+
+    /**
+     * Callbacks for package changes to this and related managed profiles.
+     */
+    public static abstract class OnAppsChangedCallback {
+        /**
+         * Indicates that a package was removed from the specified profile.
+         *
+         * @param packageName The name of the package that was removed.
+         * @param user The UserHandle of the profile that generated the change.
+         */
+        abstract public void onPackageRemoved(String packageName, UserHandle user);
+
+        /**
+         * Indicates that a package was added to the specified profile.
+         *
+         * @param packageName The name of the package that was added.
+         * @param user The UserHandle of the profile that generated the change.
+         */
+        abstract public void onPackageAdded(String packageName, UserHandle user);
+
+        /**
+         * Indicates that a package was modified in the specified profile.
+         *
+         * @param packageName The name of the package that has changed.
+         * @param user The UserHandle of the profile that generated the change.
+         */
+        abstract public void onPackageChanged(String packageName, UserHandle user);
+
+        /**
+         * Indicates that one or more packages have become available. For
+         * example, this can happen when a removable storage card has
+         * reappeared.
+         *
+         * @param packageNames The names of the packages that have become
+         *            available.
+         * @param user The UserHandle of the profile that generated the change.
+         * @param replacing Indicates whether these packages are replacing
+         *            existing ones.
+         */
+        abstract public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing);
+
+        /**
+         * Indicates that one or more packages have become unavailable. For
+         * example, this can happen when a removable storage card has been
+         * removed.
+         *
+         * @param packageNames The names of the packages that have become
+         *            unavailable.
+         * @param user The UserHandle of the profile that generated the change.
+         * @param replacing Indicates whether the packages are about to be
+         *            replaced with new versions.
+         */
+        abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing);
+    }
 
     /**
      * Callbacks for package changes to this and related managed profiles.
@@ -270,7 +329,7 @@
         synchronized (this) {
             if (listener != null && !mListeners.contains(listener)) {
                 mListeners.add(listener);
-                if (mListeners.size() == 1) {
+                if (mListeners.size() == 1 && mCallbacks.size() == 0) {
                     try {
                         mService.addOnAppsChangedListener(mAppsChangedListener);
                     } catch (RemoteException re) {
@@ -289,7 +348,44 @@
     public void removeOnAppsChangedListener(OnAppsChangedListener listener) {
         synchronized (this) {
             mListeners.remove(listener);
-            if (mListeners.size() == 0) {
+            if (mListeners.size() == 0 && mCallbacks.size() == 0) {
+                try {
+                    mService.removeOnAppsChangedListener(mAppsChangedListener);
+                } catch (RemoteException re) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a callback for changes to packages in current and managed profiles.
+     *
+     * @param callback The callback to add.
+     */
+    public void addOnAppsChangedCallback(OnAppsChangedCallback callback) {
+        synchronized (this) {
+            if (callback != null && !mCallbacks.contains(callback)) {
+                mCallbacks.add(callback);
+                if (mCallbacks.size() == 1 && mListeners.size() == 0) {
+                    try {
+                        mService.addOnAppsChangedListener(mAppsChangedListener);
+                    } catch (RemoteException re) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes a callback that was previously added.
+     *
+     * @param callback The callback to remove.
+     * @see #addOnAppsChangedListener(OnAppsChangedCallback)
+     */
+    public void removeOnAppsChangedCallback(OnAppsChangedCallback callback) {
+        synchronized (this) {
+            mListeners.remove(callback);
+            if (mListeners.size() == 0 && mCallbacks.size() == 0) {
                 try {
                     mService.removeOnAppsChangedListener(mAppsChangedListener);
                 } catch (RemoteException re) {
@@ -309,6 +405,9 @@
                 for (OnAppsChangedListener listener : mListeners) {
                     listener.onPackageRemoved(user, packageName);
                 }
+                for (OnAppsChangedCallback callback : mCallbacks) {
+                    callback.onPackageRemoved(packageName, user);
+                }
             }
         }
 
@@ -321,6 +420,9 @@
                 for (OnAppsChangedListener listener : mListeners) {
                     listener.onPackageChanged(user, packageName);
                 }
+                for (OnAppsChangedCallback callback : mCallbacks) {
+                    callback.onPackageChanged(packageName, user);
+                }
             }
         }
 
@@ -333,6 +435,9 @@
                 for (OnAppsChangedListener listener : mListeners) {
                     listener.onPackageAdded(user, packageName);
                 }
+                for (OnAppsChangedCallback callback : mCallbacks) {
+                    callback.onPackageAdded(packageName, user);
+                }
             }
         }
 
@@ -346,6 +451,9 @@
                 for (OnAppsChangedListener listener : mListeners) {
                     listener.onPackagesAvailable(user, packageNames, replacing);
                 }
+                for (OnAppsChangedCallback callback : mCallbacks) {
+                    callback.onPackagesAvailable(packageNames, user, replacing);
+                }
             }
         }
 
@@ -359,7 +467,10 @@
                 for (OnAppsChangedListener listener : mListeners) {
                     listener.onPackagesUnavailable(user, packageNames, replacing);
                 }
-            }
+                for (OnAppsChangedCallback callback : mCallbacks) {
+                    callback.onPackagesUnavailable(packageNames, user, replacing);
+                }
+           }
         }
     };
 }
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..9ac433f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -26,8 +26,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Environment;
@@ -195,6 +197,28 @@
      */
     public static final int MATCH_DEFAULT_ONLY   = 0x00010000;
 
+    /**
+     * Resolution and querying flag: do not resolve intents cross-profile.
+     * @hide
+     */
+    public static final int NO_CROSS_PROFILE = 0x00020000;
+
+    /**
+     * Flag for {@link addCrossProfileIntentFilter}: if the cross-profile intent has been set by the
+     * profile owner.
+     * @hide
+     */
+    public static final int SET_BY_PROFILE_OWNER= 0x00000001;
+
+    /**
+     * Flag for {@link addCrossProfileIntentFilter}: if this flag is set:
+     * when resolving an intent that matches the {@link CrossProfileIntentFilter}, the current
+     * profile will be skipped.
+     * Only activities in the target user can respond to the intent.
+     * @hide
+     */
+    public static final int SKIP_CURRENT_PROFILE = 0x00000002;
+
     /** @hide */
     @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED})
     @Retention(RetentionPolicy.SOURCE)
@@ -520,7 +544,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 +1625,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 +1639,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
@@ -2292,6 +2316,7 @@
      * @see #MATCH_DEFAULT_ONLY
      * @see #GET_INTENT_FILTERS
      * @see #GET_RESOLVED_FILTER
+     * @see #NO_CROSS_PROFILE
      * @hide
      */
     public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
@@ -2453,6 +2478,19 @@
             int flags);
 
     /**
+     * Find a single content provider by its base path name.
+     *
+     * @param name The name of the provider to find.
+     * @param flags Additional option flags.  Currently should always be 0.
+     * @param userId The user id.
+     *
+     * @return ContentProviderInfo Information about the provider, if found,
+     *         else null.
+     * @hide
+     */
+    public abstract ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId);
+
+    /**
      * Retrieve content provider information.
      *
      * <p><em>Note: unlike most other methods, an empty result set is indicated
@@ -2869,20 +2907,19 @@
      *
      */
     public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
-        PackageParser packageParser = new PackageParser(archiveFilePath);
-        DisplayMetrics metrics = new DisplayMetrics();
-        metrics.setToDefaults();
-        final File sourceFile = new File(archiveFilePath);
-        PackageParser.Package pkg = packageParser.parsePackage(
-                sourceFile, archiveFilePath, metrics, 0);
-        if (pkg == null) {
+        final PackageParser parser = new PackageParser();
+        final File apkFile = new File(archiveFilePath);
+        try {
+            PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
+            if ((flags & GET_SIGNATURES) != 0) {
+                parser.collectCertificates(pkg, 0);
+                parser.collectManifestDigest(pkg);
+            }
+            PackageUserState state = new PackageUserState();
+            return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
+        } catch (PackageParserException e) {
             return null;
         }
-        if ((flags & GET_SIGNATURES) != 0) {
-            packageParser.collectCertificates(pkg, 0);
-        }
-        PackageUserState state = new PackageUserState();
-        return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
     }
 
     /**
@@ -3454,7 +3491,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 +3529,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 +3613,34 @@
     }
 
     /**
-     * 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, int sourceUserId,
+            int targetUserId, int flags);
 
     /**
-     * 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
+     * Clearing {@link CrossProfileIntentFilter}s which have the specified user as their
+     * source, and have been set by the profile owner
+     * @param sourceUserId
      * @hide
      */
-    public abstract void clearForwardingIntentFilters(int userIdOrig);
+    public abstract void clearCrossProfileIntentFilters(int sourceUserId);
+
+    /**
+     * Forwards all intents for {@link packageName} for user {@link sourceUserId} to user
+     * {@link targetUserId}.
+     * @hide
+     */
+    public abstract void addCrossProfileIntentsForPackage(String packageName,
+            int sourceUserId, int targetUserId);
+    /**
+     * @hide
+     */
+    public abstract Bitmap getUserIcon(int userId);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8965faa..b40a441 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,8 +16,14 @@
 
 package android.content.pm;
 
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 
 import android.content.ComponentName;
 import android.content.Intent;
@@ -31,6 +37,9 @@
 import android.os.Bundle;
 import android.os.PatternMatcher;
 import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Base64;
 import android.util.DisplayMetrics;
@@ -39,12 +48,18 @@
 import android.util.Slog;
 import android.util.TypedValue;
 
-import java.io.BufferedInputStream;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
@@ -56,21 +71,19 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.jar.StrictJarFile;
 import java.util.zip.ZipEntry;
 
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 /**
  * Package archive parsing
  *
@@ -81,6 +94,8 @@
     private static final boolean DEBUG_PARSER = false;
     private static final boolean DEBUG_BACKUP = false;
 
+    // TODO: switch outError users to PackageParserException
+
     /** File name in an APK for the Android manifest. */
     private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
 
@@ -149,17 +164,21 @@
                     android.os.Build.VERSION_CODES.JELLY_BEAN)
     };
 
+    /**
+     * @deprecated callers should move to explicitly passing around source path.
+     */
+    @Deprecated
     private String mArchiveSourcePath;
+
     private String[] mSeparateProcesses;
     private boolean mOnlyCoreApps;
+    private DisplayMetrics mMetrics;
+
     private static final int SDK_VERSION = Build.VERSION.SDK_INT;
     private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
 
     private int mParseError = PackageManager.INSTALL_SUCCEEDED;
 
-    private static final Object mSync = new Object();
-    private static WeakReference<byte[]> mReadBuffer;
-
     private static boolean sCompatibilityModeEnabled = true;
     private static final int PARSE_DEFAULT_INSTALL_LOCATION =
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
@@ -207,10 +226,10 @@
         }
     }
 
-    /* Light weight package info.
-     * @hide
+    /**
+     * Lightweight parsed details about a single APK file.
      */
-    public static class PackageLite {
+    public static class ApkLite {
         public final String packageName;
         public final String splitName;
         public final int versionCode;
@@ -218,7 +237,7 @@
         public final VerifierInfo[] verifiers;
         public final Signature[] signatures;
 
-        public PackageLite(String packageName, String splitName, int versionCode,
+        public ApkLite(String packageName, String splitName, int versionCode,
                 int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) {
             this.packageName = packageName;
             this.splitName = splitName;
@@ -243,8 +262,9 @@
 
     private static final String TAG = "PackageParser";
 
-    public PackageParser(String archiveSourcePath) {
-        mArchiveSourcePath = archiveSourcePath;
+    public PackageParser() {
+        mMetrics = new DisplayMetrics();
+        mMetrics.setToDefaults();
     }
 
     public void setSeparateProcesses(String[] procs) {
@@ -255,6 +275,14 @@
         mOnlyCoreApps = onlyCoreApps;
     }
 
+    public void setDisplayMetrics(DisplayMetrics metrics) {
+        mMetrics = metrics;
+    }
+
+    private static final boolean isPackageFilename(File file) {
+        return isPackageFilename(file.getName());
+    }
+
     private static final boolean isPackageFilename(String name) {
         return name.endsWith(".apk");
     }
@@ -468,23 +496,21 @@
         return pi;
     }
 
-    private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
-            byte[] readBuffer) {
+    private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
+            throws PackageParserException {
+        InputStream is = null;
         try {
             // We must read the stream for the JarEntry to retrieve
             // its certificates.
-            InputStream is = new BufferedInputStream(jarFile.getInputStream(je));
-            while (is.read(readBuffer, 0, readBuffer.length) != -1) {
-                // not using
-            }
-            is.close();
-            return je != null ? jarFile.getCertificateChains(je) : null;
-        } catch (IOException e) {
-            Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
+            is = jarFile.getInputStream(entry);
+            readFullyIgnoringContents(is);
+            return jarFile.getCertificateChains(entry);
+        } catch (IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed reading " + entry.getName() + " in " + jarFile, e);
+        } finally {
+            IoUtils.closeQuietly(is);
         }
-        return null;
     }
 
     public final static int PARSE_IS_SYSTEM = 1<<0;
@@ -496,39 +522,148 @@
     public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
     public final static int PARSE_IS_PRIVILEGED = 1<<7;
     public final static int PARSE_GET_SIGNATURES = 1<<8;
+    public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
 
-    public int getParseError() {
-        return mParseError;
+    private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
+
+    /**
+     * Used to sort a set of APKs based on their split names, always placing the
+     * base APK (with {@code null} split name) first.
+     */
+    private static class SplitNameComparator implements Comparator<String> {
+        @Override
+        public int compare(String lhs, String rhs) {
+            if (lhs == null) {
+                return -1;
+            } else if (rhs == null) {
+                return 1;
+            } else {
+                return lhs.compareTo(rhs);
+            }
+        }
     }
 
-    public Package parsePackage(File sourceFile, String destCodePath,
-            DisplayMetrics metrics, int flags) {
-        return parsePackage(sourceFile, destCodePath, metrics, flags, false);
+    /**
+     * Parse all APKs contained in the given directory, treating them as a
+     * single package. This also performs sanity checking, such as requiring
+     * identical package name and version codes, a single base APK, and unique
+     * split names.
+     * <p>
+     * Note that this <em>does not</em> perform signature verification; that
+     * must be done separately in {@link #collectCertificates(Package, int)}.
+     */
+    public Package parseSplitPackage(File apkDir, int flags) throws PackageParserException {
+        final File[] files = apkDir.listFiles();
+        if (ArrayUtils.isEmpty(files)) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "No packages found in split");
+        }
+
+        String packageName = null;
+        int versionCode = 0;
+
+        final ArrayMap<String, File> apks = new ArrayMap<>();
+        for (File file : files) {
+            if (file.isFile() && isPackageFilename(file)) {
+                final ApkLite lite = parseApkLite(file, 0);
+
+                // Assert that all package names and version codes are
+                // consistent with the first one we encounter.
+                if (packageName == null) {
+                    packageName = lite.packageName;
+                    versionCode = lite.versionCode;
+                } else {
+                    if (!packageName.equals(lite.packageName)) {
+                        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                                "Inconsistent package " + lite.packageName + " in " + file
+                                + "; expected " + packageName);
+                    }
+                    if (versionCode != lite.versionCode) {
+                        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                                "Inconsistent version " + lite.versionCode + " in " + file
+                                + "; expected " + versionCode);
+                    }
+                }
+
+                // Assert that each split is defined only once
+                if (apks.put(lite.splitName, file) != null) {
+                    throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Split name " + lite.splitName
+                            + " defined more than once; most recent was " + file);
+                }
+            }
+        }
+
+        final File baseFile = apks.remove(null);
+        if (baseFile == null) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                    "Missing base APK in " + apkDir);
+        }
+
+        // Always apply deterministic ordering based on splitName
+        final int size = apks.size();
+
+        final String[] splitNames = apks.keySet().toArray(new String[size]);
+        Arrays.sort(splitNames, sSplitNameComparator);
+
+        final File[] splitFiles = new File[size];
+        for (int i = 0; i < size; i++) {
+            splitFiles[i] = apks.get(splitNames[i]);
+        }
+
+        final Package pkg = parseBaseApk(baseFile, flags);
+        if (pkg == null) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Failed to parse base APK: " + baseFile);
+        }
+
+        for (File splitFile : splitFiles) {
+            parseSplitApk(pkg, splitFile, flags);
+        }
+
+        return pkg;
     }
 
-    public Package parsePackage(File sourceFile, String destCodePath,
-            DisplayMetrics metrics, int flags, boolean trustedOverlay) {
+    /**
+     * Parse the given APK file, treating it as as a single monolithic package.
+     * <p>
+     * Note that this <em>does not</em> perform signature verification; that
+     * must be done separately in {@link #collectCertificates(Package, int)}.
+     */
+    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
+        final Package pkg = parseBaseApk(apkFile, flags);
+        if (pkg != null) {
+            return pkg;
+        } else {
+            throw new PackageParserException(mParseError, "Failed to parse " + apkFile);
+        }
+    }
+
+    private Package parseBaseApk(File apkFile, int flags) {
+        final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+
         mParseError = PackageManager.INSTALL_SUCCEEDED;
 
-        mArchiveSourcePath = sourceFile.getPath();
-        if (!sourceFile.isFile()) {
-            Slog.w(TAG, "Skipping dir: " + mArchiveSourcePath);
+        final String apkPath = apkFile.getAbsolutePath();
+        mArchiveSourcePath = apkFile.getAbsolutePath();
+        if (!apkFile.isFile()) {
+            Slog.w(TAG, "Skipping dir: " + apkPath);
             mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
             return null;
         }
-        if (!isPackageFilename(sourceFile.getName())
+        if (!isPackageFilename(apkFile.getName())
                 && (flags&PARSE_MUST_BE_APK) != 0) {
             if ((flags&PARSE_IS_SYSTEM) == 0) {
                 // We expect to have non-.apk files in the system dir,
                 // so don't warn about them.
-                Slog.w(TAG, "Skipping non-package file: " + mArchiveSourcePath);
+                Slog.w(TAG, "Skipping non-package file: " + apkPath);
             }
             mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
             return null;
         }
 
         if (DEBUG_JAR)
-            Slog.d(TAG, "Scanning package: " + mArchiveSourcePath);
+            Slog.d(TAG, "Scanning package: " + apkPath);
 
         XmlResourceParser parser = null;
         AssetManager assmgr = null;
@@ -536,19 +671,18 @@
         boolean assetError = true;
         try {
             assmgr = new AssetManager();
-            int cookie = assmgr.addAssetPath(mArchiveSourcePath);
+            int cookie = assmgr.addAssetPath(apkPath);
             if (cookie != 0) {
-                res = new Resources(assmgr, metrics, null);
+                res = new Resources(assmgr, mMetrics, null);
                 assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                         Build.VERSION.RESOURCES_SDK_INT);
                 parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
                 assetError = false;
             } else {
-                Slog.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
+                Slog.w(TAG, "Failed adding asset path:" + apkPath);
             }
         } catch (Exception e) {
-            Slog.w(TAG, "Unable to read AndroidManifest.xml of "
-                    + mArchiveSourcePath, e);
+            Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e);
         }
         if (assetError) {
             if (assmgr != null) assmgr.close();
@@ -560,21 +694,20 @@
         Exception errorException = null;
         try {
             // XXXX todo: need to figure out correct configuration.
-            pkg = parsePackage(res, parser, flags, trustedOverlay, errorText);
+            pkg = parseBaseApk(res, parser, flags, trustedOverlay, errorText);
         } catch (Exception e) {
             errorException = e;
             mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
         }
 
-
         if (pkg == null) {
             // If we are only parsing core apps, then a null with INSTALL_SUCCEEDED
             // just means to skip this app so don't make a fuss about it.
             if (!mOnlyCoreApps || mParseError != PackageManager.INSTALL_SUCCEEDED) {
                 if (errorException != null) {
-                    Slog.w(TAG, mArchiveSourcePath, errorException);
+                    Slog.w(TAG, apkPath, errorException);
                 } else {
-                    Slog.w(TAG, mArchiveSourcePath + " (at "
+                    Slog.w(TAG, apkPath + " (at "
                             + parser.getPositionDescription()
                             + "): " + errorText[0]);
                 }
@@ -590,24 +723,32 @@
         parser.close();
         assmgr.close();
 
-        // Set code and resource paths
-        pkg.mPath = destCodePath;
-        pkg.mScanPath = mArchiveSourcePath;
-        //pkg.applicationInfo.sourceDir = destCodePath;
-        //pkg.applicationInfo.publicSourceDir = destRes;
+        pkg.codePath = apkPath;
         pkg.mSignatures = null;
 
         return pkg;
     }
 
+    private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException {
+        final String apkPath = apkFile.getAbsolutePath();
+        mArchiveSourcePath = apkFile.getAbsolutePath();
+
+        // TODO: expand split APK parsing
+        pkg.splitCodePaths = ArrayUtils.appendElement(String.class, pkg.splitCodePaths,
+                apkFile.getAbsolutePath());
+    }
+
     /**
      * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
      * APK. If it successfully scanned the package and found the
      * {@code AndroidManifest.xml}, {@code true} is returned.
      */
-    public boolean collectManifestDigest(Package pkg) {
+    public void collectManifestDigest(Package pkg) throws PackageParserException {
+        pkg.manifestDigest = null;
+
+        // TODO: extend to gather digest for split APKs
         try {
-            final StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
+            final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
             try {
                 final ZipEntry je = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
                 if (je != null) {
@@ -616,180 +757,127 @@
             } finally {
                 jarFile.close();
             }
-            return true;
-        } catch (IOException e) {
-            return false;
+        } catch (IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    "Failed to collect manifest digest");
         }
     }
 
-    public boolean collectCertificates(Package pkg, int flags) {
+    /**
+     * Collect certificates from all the APKs described in the given package,
+     * populating {@link Package#mSignatures}. This also asserts that all APK
+     * contents are signed correctly and consistently.
+     */
+    public void collectCertificates(Package pkg, int flags) throws PackageParserException {
+        pkg.mCertificates = null;
         pkg.mSignatures = null;
+        pkg.mSigningKeys = null;
 
-        WeakReference<byte[]> readBufferRef;
-        byte[] readBuffer = null;
-        synchronized (mSync) {
-            readBufferRef = mReadBuffer;
-            if (readBufferRef != null) {
-                mReadBuffer = null;
-                readBuffer = readBufferRef.get();
-            }
-            if (readBuffer == null) {
-                readBuffer = new byte[8192];
-                readBufferRef = new WeakReference<byte[]>(readBuffer);
+        collectCertificates(pkg, new File(pkg.codePath), flags);
+
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (String splitCodePath : pkg.splitCodePaths) {
+                collectCertificates(pkg, new File(splitCodePath), flags);
             }
         }
+    }
 
+    private static void collectCertificates(Package pkg, File apkFile, int flags)
+            throws PackageParserException {
+        final String apkPath = apkFile.getAbsolutePath();
+
+        StrictJarFile jarFile = null;
         try {
-            StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
+            jarFile = new StrictJarFile(apkPath);
 
-            Certificate[][] certs = null;
+            // Always verify manifest, regardless of source
+            final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+            if (manifestEntry == null) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Package " + apkPath + " has no manifest");
+            }
 
-            if ((flags&PARSE_IS_SYSTEM) != 0) {
-                // If this package comes from the system image, then we
-                // can trust it...  we'll just use the AndroidManifest.xml
-                // to retrieve its signatures, not validating all of the
-                // files.
-                ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
-                certs = loadCertificates(jarFile, jarEntry, readBuffer);
-                if (certs == null) {
-                    Slog.e(TAG, "Package " + pkg.packageName
-                            + " has no certificates at entry "
-                            + jarEntry.getName() + "; ignoring!");
-                    jarFile.close();
-                    mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-                    return false;
+            final List<ZipEntry> toVerify = new ArrayList<>();
+            toVerify.add(manifestEntry);
+
+            // If we're parsing an untrusted package, verify all contents
+            if ((flags & PARSE_IS_SYSTEM) == 0) {
+                final Iterator<ZipEntry> i = jarFile.iterator();
+                while (i.hasNext()) {
+                    final ZipEntry entry = i.next();
+
+                    if (entry.isDirectory()) continue;
+                    if (entry.getName().startsWith("META-INF/")) continue;
+                    if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;
+
+                    toVerify.add(entry);
                 }
-                if (DEBUG_JAR) {
-                    Slog.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry
-                            + " certs=" + (certs != null ? certs.length : 0));
-                    if (certs != null) {
-                        final int N = certs.length;
-                        for (int i=0; i<N; i++) {
-                            Slog.i(TAG, "  Public key: "
-                                    + certs[i][0].getPublicKey().getEncoded()
-                                    + " " + certs[i][0].getPublicKey());
-                        }
-                    }
+            }
+
+            // Verify that entries are signed consistently with the first entry
+            // we encountered. Note that for splits, certificates may have
+            // already been populated during an earlier parse of a base APK.
+            for (ZipEntry entry : toVerify) {
+                final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
+                if (ArrayUtils.isEmpty(entryCerts)) {
+                    throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                            "Package " + apkPath + " has no certificates at entry "
+                            + entry.getName());
                 }
-            } else {
-                Iterator<ZipEntry> entries = jarFile.iterator();
-                while (entries.hasNext()) {
-                    final ZipEntry je = entries.next();
-                    if (je.isDirectory()) continue;
 
-                    final String name = je.getName();
-
-                    if (name.startsWith("META-INF/"))
-                        continue;
-
-                    if (ANDROID_MANIFEST_FILENAME.equals(name)) {
-                        pkg.manifestDigest =
-                                ManifestDigest.fromInputStream(jarFile.getInputStream(je));
+                if (pkg.mCertificates == null) {
+                    pkg.mCertificates = entryCerts;
+                    pkg.mSignatures = convertToSignatures(entryCerts);
+                    pkg.mSigningKeys = new ArraySet<>();
+                    for (int i = 0; i < entryCerts.length; i++) {
+                        pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
                     }
-
-                    final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer);
-                    if (DEBUG_JAR) {
-                        Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
-                                + ": certs=" + certs + " ("
-                                + (certs != null ? certs.length : 0) + ")");
-                    }
-
-                    if (localCerts == null) {
-                        Slog.e(TAG, "Package " + pkg.packageName
-                                + " has no certificates at entry "
-                                + je.getName() + "; ignoring!");
-                        jarFile.close();
-                        mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-                        return false;
-                    } else if (certs == null) {
-                        certs = localCerts;
-                    } else {
-                        // Ensure all certificates match.
-                        for (int i=0; i<certs.length; i++) {
-                            boolean found = false;
-                            for (int j=0; j<localCerts.length; j++) {
-                                if (certs[i] != null &&
-                                        certs[i].equals(localCerts[j])) {
-                                    found = true;
-                                    break;
-                                }
-                            }
-                            if (!found || certs.length != localCerts.length) {
-                                Slog.e(TAG, "Package " + pkg.packageName
+                } else {
+                    final boolean certsMatch = (pkg.mCertificates.length == entryCerts.length)
+                            && ArrayUtils.containsAll(pkg.mCertificates, entryCerts)
+                            && ArrayUtils.containsAll(entryCerts, pkg.mCertificates);
+                    if (!certsMatch) {
+                        throw new PackageParserException(
+                                INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
                                         + " has mismatched certificates at entry "
-                                        + je.getName() + "; ignoring!");
-                                jarFile.close();
-                                mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-                                return false;
-                            }
-                        }
+                                        + entry.getName());
                     }
                 }
             }
-            jarFile.close();
-
-            synchronized (mSync) {
-                mReadBuffer = readBufferRef;
-            }
-
-            if (!ArrayUtils.isEmpty(certs)) {
-                pkg.mSignatures = convertToSignatures(certs);
-            } else {
-                Slog.e(TAG, "Package " + pkg.packageName
-                        + " has no certificates; ignoring!");
-                mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-                return false;
-            }
-
-            // Add the signing KeySet to the system
-            pkg.mSigningKeys = new HashSet<PublicKey>();
-            for (int i=0; i < certs.length; i++) {
-                pkg.mSigningKeys.add(certs[i][0].getPublicKey());
-            }
-
-        } catch (CertificateEncodingException e) {
-            Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
-            return false;
-        } catch (IOException e) {
-            Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
-            return false;
-        } catch (SecurityException e) {
-            Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
-            return false;
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
-            mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
-            return false;
+        } catch (GeneralSecurityException | IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+                    "Failed to collect certificates from " + apkPath, e);
+        } finally {
+            closeQuietly(jarFile);
         }
-
-        return true;
     }
 
     /**
      * Only collect certificates on the manifest; does not validate signatures
      * across remainder of package.
      */
-    private static Signature[] collectCertificates(String packageFilePath) {
+    private static Signature[] collectManifestCertificates(File apkFile)
+            throws PackageParserException {
+        final String apkPath = apkFile.getAbsolutePath();
         try {
-            final StrictJarFile jarFile = new StrictJarFile(packageFilePath);
+            final StrictJarFile jarFile = new StrictJarFile(apkPath);
             try {
                 final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
-                if (jarEntry != null) {
-                    final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null);
-                    return convertToSignatures(certs);
+                if (jarEntry == null) {
+                    throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                            "Package " + apkPath + " has no manifest");
                 }
+
+                final Certificate[][] certs = loadCertificates(jarFile, jarEntry);
+                return convertToSignatures(certs);
+
             } finally {
                 jarFile.close();
             }
-        } catch (GeneralSecurityException e) {
-            Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
-        } catch (IOException e) {
-            Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+        } catch (GeneralSecurityException | IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+                    "Failed to collect certificates from " + apkPath, e);
         }
-        return null;
     }
 
     private static Signature[] convertToSignatures(Certificate[][] certs)
@@ -801,67 +889,55 @@
         return res;
     }
 
-    /*
-     * Utility method that retrieves just the package name and install
-     * location from the apk location at the given file path.
-     * @param packageFilePath file location of the apk
-     * @param flags Special parse flags
-     * @return PackageLite object with package information or null on failure.
+    /**
+     * Utility method that retrieves lightweight details about a single APK
+     * file, including package name, split name, and install location.
+     *
+     * @param apkFile path to a single APK
+     * @param flags optional parse flags, such as {@link #PARSE_GET_SIGNATURES}
      */
-    public static PackageLite parsePackageLite(String packageFilePath, int flags) {
+    public static ApkLite parseApkLite(File apkFile, int flags)
+            throws PackageParserException {
+        final String apkPath = apkFile.getAbsolutePath();
+
         AssetManager assmgr = null;
-        final XmlResourceParser parser;
-        final Resources res;
+        XmlResourceParser parser = null;
         try {
             assmgr = new AssetManager();
             assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
 
-            int cookie = assmgr.addAssetPath(packageFilePath);
+            int cookie = assmgr.addAssetPath(apkPath);
             if (cookie == 0) {
-                return null;
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                        "Failed to parse " + apkPath);
             }
 
             final DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
-            res = new Resources(assmgr, metrics, null);
+
+            final Resources res = new Resources(assmgr, metrics, null);
             parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
-        } catch (Exception e) {
-            if (assmgr != null) assmgr.close();
-            Slog.w(TAG, "Unable to read AndroidManifest.xml of "
-                    + packageFilePath, e);
-            return null;
-        }
 
-        // Only collect certificates on the manifest; does not validate
-        // signatures across remainder of package.
-        final Signature[] signatures;
-        if ((flags & PARSE_GET_SIGNATURES) != 0) {
-            signatures = collectCertificates(packageFilePath);
-        } else {
-            signatures = null;
-        }
+            // Only collect certificates on the manifest; does not validate
+            // signatures across remainder of package.
+            final Signature[] signatures;
+            if ((flags & PARSE_GET_SIGNATURES) != 0) {
+                signatures = collectManifestCertificates(apkFile);
+            } else {
+                signatures = null;
+            }
 
-        final AttributeSet attrs = parser;
-        final String errors[] = new String[1];
-        PackageLite packageLite = null;
-        try {
-            packageLite = parsePackageLite(res, parser, attrs, flags, signatures, errors);
-        } catch (PackageParserException e) {
-            Slog.w(TAG, packageFilePath, e);
-        } catch (IOException e) {
-            Slog.w(TAG, packageFilePath, e);
-        } catch (XmlPullParserException e) {
-            Slog.w(TAG, packageFilePath, e);
+            final AttributeSet attrs = parser;
+            return parseApkLite(res, parser, attrs, flags, signatures);
+
+        } catch (XmlPullParserException | IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed to parse " + apkPath, e);
         } finally {
             if (parser != null) parser.close();
             if (assmgr != null) assmgr.close();
         }
-        if (packageLite == null) {
-            Slog.e(TAG, "parsePackageLite error: " + errors[0]);
-            return null;
-        }
-        return packageLite;
     }
 
     private static String validateName(String name, boolean requiresSeparator) {
@@ -917,12 +993,16 @@
             }
         }
 
-        final String splitName = attrs.getAttributeValue(null, "split");
+        String splitName = attrs.getAttributeValue(null, "split");
         if (splitName != null) {
-            final String error = validateName(splitName, true);
-            if (error != null) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
-                        "Invalid manifest split: " + error);
+            if (splitName.length() == 0) {
+                splitName = null;
+            } else {
+                final String error = validateName(splitName, true);
+                if (error != null) {
+                    throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+                            "Invalid manifest split: " + error);
+                }
             }
         }
 
@@ -930,9 +1010,9 @@
                 (splitName != null) ? splitName.intern() : splitName);
     }
 
-    private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags, Signature[] signatures, String[] outError)
-            throws IOException, XmlPullParserException, PackageParserException {
+    private static ApkLite parseApkLite(Resources res, XmlPullParser parser,
+            AttributeSet attrs, int flags, Signature[] signatures) throws IOException,
+            XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
 
         int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -965,14 +1045,14 @@
             }
 
             if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
-                final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError);
+                final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags);
                 if (verifier != null) {
                     verifiers.add(verifier);
                 }
             }
         }
 
-        return new PackageLite(packageSplit.first, packageSplit.second, versionCode,
+        return new ApkLite(packageSplit.first, packageSplit.second, versionCode,
                 installLocation, verifiers, signatures);
     }
 
@@ -988,9 +1068,8 @@
         return new Signature(sig);
     }
 
-    private Package parsePackage(
-        Resources res, XmlResourceParser parser, int flags, boolean trustedOverlay,
-        String[] outError) throws XmlPullParserException, IOException {
+    private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
+            boolean trustedOverlay, String[] outError) throws XmlPullParserException, IOException {
         AttributeSet attrs = parser;
 
         mParseInstrumentationArgs = null;
@@ -1019,7 +1098,13 @@
             }
         }
 
-        final Package pkg = new Package(pkgName, splitName);
+        if (!TextUtils.isEmpty(splitName)) {
+            outError[0] = "Expected base APK, but found split " + splitName;
+            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+            return null;
+        }
+
+        final Package pkg = new Package(pkgName);
         boolean foundApp = false;
 
         TypedArray sa = res.obtainAttributes(attrs,
@@ -1710,7 +1795,7 @@
             }
         }
 
-        owner.mKeySetMapping = new HashMap<String, Set<PublicKey>>();
+        owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>();
         for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) {
             PublicKey key = e.getKey();
             Set<String> keySetNames = e.getValue();
@@ -1718,7 +1803,7 @@
                 if (owner.mKeySetMapping.containsKey(alias)) {
                     owner.mKeySetMapping.get(alias).add(key);
                 } else {
-                    Set<PublicKey> keys = new HashSet<PublicKey>();
+                    ArraySet<PublicKey> keys = new ArraySet<PublicKey>();
                     keys.add(key);
                     owner.mKeySetMapping.put(alias, keys);
                 }
@@ -2005,6 +2090,11 @@
                         false)) {
                     ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
                 }
+                if (sa.getBoolean(
+                        com.android.internal.R.styleable.AndroidManifestApplication_fullBackupOnly,
+                        false)) {
+                    ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+                }
             }
         }
 
@@ -2174,7 +2264,6 @@
         }
 
         final int innerDepth = parser.getDepth();
-
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -2525,10 +2614,9 @@
                     com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
                     0);
 
-            if (sa.getBoolean(
-                    com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) {
-                a.info.flags |= ActivityInfo.FLAG_PERSISTABLE;
-            }
+            a.info.persistableMode = sa.getInteger(
+                    com.android.internal.R.styleable.AndroidManifestActivity_persistableMode,
+                    ActivityInfo.PERSIST_ROOT_ONLY);
 
             if (sa.getBoolean(
                     com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded,
@@ -2541,6 +2629,12 @@
                     false)) {
                 a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
             }
+
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
+                    false)) {
+                a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+            }
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
@@ -2551,13 +2645,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 +3004,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 +3278,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();
@@ -3346,8 +3440,7 @@
     }
 
     private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException,
-            IOException {
+            AttributeSet attrs, int flags) {
         final TypedArray sa = res.obtainAttributes(attrs,
                 com.android.internal.R.styleable.AndroidManifestPackageVerifier);
 
@@ -3581,10 +3674,20 @@
         return true;
     }
 
+    /**
+     * Representation of a full package parsed from APK files on disk. A package
+     * consists of a single base APK, and zero or more split APKs.
+     */
     public final static class Package {
 
         public String packageName;
-        public String splitName;
+
+        // TODO: work towards making these paths invariant
+
+        /** Base APK */
+        public String codePath;
+        /** Split APKs, ordered by parsed splitName */
+        public String[] splitCodePaths;
 
         // For now we only support one application per package.
         public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -3616,9 +3719,6 @@
         // We store the application meta-data independently to avoid multiple unwanted references
         public Bundle mAppMetaData = null;
 
-        // If this is a 3rd party app, this is the path of the zip file.
-        public String mPath;
-
         // The version code declared for this package.
         public int mVersionCode;
         
@@ -3632,16 +3732,13 @@
         public int mSharedUserLabel;
 
         // Signatures that were read from the package.
-        public Signature mSignatures[];
+        public Signature[] mSignatures;
+        public Certificate[][] mCertificates;
 
         // For use by package manager service for quick lookup of
         // preferred up order.
         public int mPreferredOrder = 0;
 
-        // For use by the package manager to keep track of the path to the
-        // file an app came from.
-        public String mScanPath;
-
         // For use by package manager to keep track of where it needs to do dexopt.
         public boolean mDexOptNeeded = true;
 
@@ -3698,16 +3795,24 @@
         /**
          * Data used to feed the KeySetManager
          */
-        public Set<PublicKey> mSigningKeys;
-        public Map<String, Set<PublicKey>> mKeySetMapping;
+        public ArraySet<PublicKey> mSigningKeys;
+        public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;
 
-        public Package(String packageName, String splitName) {
+        public Package(String packageName) {
             this.packageName = packageName;
-            this.splitName = splitName;
             applicationInfo.packageName = packageName;
             applicationInfo.uid = -1;
         }
 
+        public Collection<String> getAllCodePaths() {
+            ArrayList<String> paths = new ArrayList<>();
+            paths.add(codePath);
+            if (!ArrayUtils.isEmpty(splitCodePaths)) {
+                Collections.addAll(paths, splitCodePaths);
+            }
+            return paths;
+        }
+
         public void setPackageName(String newName) {
             packageName = newName;
             applicationInfo.packageName = newName;
@@ -4310,6 +4415,33 @@
         sCompatibilityModeEnabled = compatibilityModeEnabled;
     }
 
+    private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>();
+
+    public static long readFullyIgnoringContents(InputStream in) throws IOException {
+        byte[] buffer = sBuffer.getAndSet(null);
+        if (buffer == null) {
+            buffer = new byte[4096];
+        }
+
+        int n = 0;
+        int count = 0;
+        while ((n = in.read(buffer, 0, buffer.length)) != -1) {
+            count += n;
+        }
+
+        sBuffer.set(buffer);
+        return count;
+    }
+
+    public static void closeQuietly(StrictJarFile jarFile) {
+        if (jarFile != null) {
+            try {
+                jarFile.close();
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
     public static class PackageParserException extends Exception {
         public final int error;
 
@@ -4317,5 +4449,10 @@
             super(detailMessage);
             this.error = error;
         }
+
+        public PackageParserException(int error, String detailMessage, Throwable throwable) {
+            super(detailMessage, throwable);
+            this.error = error;
+        }
     }
 }
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 1ff41c0..1f9d60c 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -19,8 +19,11 @@
 import android.content.ComponentName;
 import android.content.IntentFilter;
 import android.graphics.drawable.Drawable;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.util.Slog;
@@ -127,6 +130,18 @@
     public String resolvePackageName;
 
     /**
+     * If not equal to UserHandle.USER_CURRENT, then the intent will be forwarded to this user.
+     * @hide
+     */
+    public int targetUserId;
+
+    /**
+     * If true, then loadIcon will return the icon of the target user.
+     * @hide
+     */
+    public boolean showTargetUserIcon;
+
+    /**
      * @hide Target comes from system process?
      */
     public boolean system;
@@ -202,6 +217,10 @@
                 return dr;
             }
         }
+        if (showTargetUserIcon) {
+            Bitmap bm = pm.getUserIcon(targetUserId);
+            return new BitmapDrawable(bm);
+        }
         return ci.loadIcon(pm);
     }
     
@@ -215,7 +234,9 @@
     public final int getIconResource() {
         if (icon != 0) return icon;
         final ComponentInfo ci = getComponentInfo();
-        if (ci != null) return ci.getIconResource();
+        if (ci != null && !showTargetUserIcon) {
+            return ci.getIconResource();
+        }
         return 0;
     }
 
@@ -250,6 +271,7 @@
     }
     
     public ResolveInfo() {
+        targetUserId = UserHandle.USER_CURRENT;
     }
 
     public ResolveInfo(ResolveInfo orig) {
@@ -266,6 +288,7 @@
         icon = orig.icon;
         resolvePackageName = orig.resolvePackageName;
         system = orig.system;
+        targetUserId = orig.targetUserId;
     }
 
     public String toString() {
@@ -285,6 +308,13 @@
         }
         sb.append(" m=0x");
         sb.append(Integer.toHexString(match));
+        if (targetUserId != UserHandle.USER_CURRENT) {
+            sb.append(" targetUserId=");
+            sb.append(targetUserId);
+        }
+        if (showTargetUserIcon) {
+            sb.append(" [showTargetUserIcon]");
+        }
         sb.append('}');
         return sb.toString();
     }
@@ -320,6 +350,8 @@
         TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
         dest.writeInt(icon);
         dest.writeString(resolvePackageName);
+        dest.writeInt(targetUserId);
+        dest.writeInt(showTargetUserIcon ? 1 : 0);
         dest.writeInt(system ? 1 : 0);
     }
 
@@ -363,6 +395,8 @@
                 = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         icon = source.readInt();
         resolvePackageName = source.readString();
+        targetUserId = source.readInt();
+        showTargetUserIcon = source.readInt() != 0;
         system = source.readInt() != 0;
     }
     
@@ -374,6 +408,13 @@
         }
 
         public final int compare(ResolveInfo a, ResolveInfo b) {
+            // We want to put the one targeted to another user at the end of the dialog.
+            if (a.targetUserId != UserHandle.USER_CURRENT) {
+                return 1;
+            }
+            if (b.targetUserId != UserHandle.USER_CURRENT) {
+                return -1;
+            }
             CharSequence  sa = a.loadLabel(mPM);
             if (sa == null) sa = a.activityInfo.name;
             CharSequence  sb = b.loadLabel(mPM);
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index a07fc97..a83bd4a 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -17,11 +17,14 @@
 package android.content.res;
 
 import android.content.pm.ActivityInfo;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.view.View;
 
+import java.text.Format;
+import java.util.ArrayList;
 import java.util.Locale;
 
 /**
@@ -1306,4 +1309,240 @@
     private static int getScreenLayoutNoDirection(int screenLayout) {
         return screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK;
     }
+
+    /**
+     *
+     * @hide
+     */
+    public static String localeToResourceQualifier(Locale locale) {
+        StringBuilder sb = new StringBuilder();
+        boolean l = (locale.getLanguage().length() != 0);
+        boolean c = (locale.getCountry().length() != 0);
+        boolean s = (locale.getScript().length() != 0);
+        boolean v = (locale.getVariant().length() != 0);
+
+        if (l) {
+            sb.append(locale.getLanguage());
+            if (c) {
+                sb.append("-r").append(locale.getCountry());
+                if (s) {
+                    sb.append("-s").append(locale.getScript());
+                    if (v) {
+                        sb.append("-v").append(locale.getVariant());
+                    }
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Returns a string representation of the configuration that can be parsed
+     * by build tools (like AAPT).
+     *
+     *
+     *
+     * @hide
+     */
+    public static String resourceQualifierString(Configuration config) {
+        ArrayList<String> parts = new ArrayList<String>();
+
+        if (config.mcc != 0) {
+            parts.add(config.mcc + "mcc");
+            if (config.mnc != 0) {
+                parts.add(config.mnc + "mnc");
+            }
+        }
+
+        if (!config.locale.getLanguage().isEmpty()) {
+            parts.add(localeToResourceQualifier(config.locale));
+        }
+
+        switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) {
+            case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR:
+                parts.add("ldltr");
+                break;
+            case Configuration.SCREENLAYOUT_LAYOUTDIR_RTL:
+                parts.add("ldrtl");
+                break;
+            default:
+                break;
+        }
+
+        if (config.smallestScreenWidthDp != 0) {
+            parts.add("sw" + config.smallestScreenWidthDp + "dp");
+        }
+
+        if (config.screenWidthDp != 0) {
+            parts.add("w" + config.screenWidthDp + "dp");
+        }
+
+        if (config.screenHeightDp != 0) {
+            parts.add("h" + config.screenHeightDp + "dp");
+        }
+
+        switch (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) {
+            case Configuration.SCREENLAYOUT_SIZE_SMALL:
+                parts.add("small");
+                break;
+            case Configuration.SCREENLAYOUT_SIZE_NORMAL:
+                parts.add("normal");
+                break;
+            case Configuration.SCREENLAYOUT_SIZE_LARGE:
+                parts.add("large");
+                break;
+            case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+                parts.add("xlarge");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) {
+            case Configuration.SCREENLAYOUT_LONG_YES:
+                parts.add("long");
+                break;
+            case Configuration.SCREENLAYOUT_LONG_NO:
+                parts.add("notlong");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.orientation) {
+            case Configuration.ORIENTATION_LANDSCAPE:
+                parts.add("land");
+                break;
+            case Configuration.ORIENTATION_PORTRAIT:
+                parts.add("port");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
+            case Configuration.UI_MODE_TYPE_APPLIANCE:
+                parts.add("appliance");
+                break;
+            case Configuration.UI_MODE_TYPE_DESK:
+                parts.add("desk");
+                break;
+            case Configuration.UI_MODE_TYPE_TELEVISION:
+                parts.add("television");
+                break;
+            case Configuration.UI_MODE_TYPE_CAR:
+                parts.add("car");
+                break;
+            case Configuration.UI_MODE_TYPE_WATCH:
+                parts.add("watch");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
+            case Configuration.UI_MODE_NIGHT_YES:
+                parts.add("night");
+                break;
+            case Configuration.UI_MODE_NIGHT_NO:
+                parts.add("notnight");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.densityDpi) {
+            case 0:
+                break;
+            case 120:
+                parts.add("ldpi");
+                break;
+            case 160:
+                parts.add("mdpi");
+                break;
+            case 213:
+                parts.add("tvdpi");
+                break;
+            case 240:
+                parts.add("hdpi");
+                break;
+            case 320:
+                parts.add("xhdpi");
+                break;
+            default:
+                parts.add(config.densityDpi + "dpi");
+                break;
+        }
+
+        switch (config.touchscreen) {
+            case Configuration.TOUCHSCREEN_NOTOUCH:
+                parts.add("notouch");
+                break;
+            case Configuration.TOUCHSCREEN_FINGER:
+                parts.add("finger");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.keyboardHidden) {
+            case Configuration.KEYBOARDHIDDEN_NO:
+                parts.add("keysexposed");
+                break;
+            case Configuration.KEYBOARDHIDDEN_YES:
+                parts.add("keyshidden");
+                break;
+            case Configuration.KEYBOARDHIDDEN_SOFT:
+                parts.add("keyssoft");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.keyboard) {
+            case Configuration.KEYBOARD_NOKEYS:
+                parts.add("nokeys");
+                break;
+            case Configuration.KEYBOARD_QWERTY:
+                parts.add("qwerty");
+                break;
+            case Configuration.KEYBOARD_12KEY:
+                parts.add("12key");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.navigationHidden) {
+            case Configuration.NAVIGATIONHIDDEN_NO:
+                parts.add("navexposed");
+                break;
+            case Configuration.NAVIGATIONHIDDEN_YES:
+                parts.add("navhidden");
+                break;
+            default:
+                break;
+        }
+
+        switch (config.navigation) {
+            case Configuration.NAVIGATION_NONAV:
+                parts.add("nonav");
+                break;
+            case Configuration.NAVIGATION_DPAD:
+                parts.add("dpad");
+                break;
+            case Configuration.NAVIGATION_TRACKBALL:
+                parts.add("trackball");
+                break;
+            case Configuration.NAVIGATION_WHEEL:
+                parts.add("wheel");
+                break;
+            default:
+                break;
+        }
+
+        parts.add("v" + Build.VERSION.SDK_INT);
+        return TextUtils.join("-", parts);
+    }
 }
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 20dcf83..d2146ac 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -885,9 +885,9 @@
 
     /**
      * Extracts theme attributes from a typed array for later resolution using
-     * {@link Theme#resolveAttributes(int[], int[])}. Removes the entries from
-     * the typed array so that subsequent calls to typed getters will return the
-     * default value without crashing.
+     * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
+     * Removes the entries from the typed array so that subsequent calls to typed
+     * getters will return the default value without crashing.
      *
      * @return an array of length {@link #getIndexCount()} populated with theme
      *         attributes, or null if there are no theme attributes in the typed
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0705e0c..cc8503b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -167,11 +167,16 @@
     private boolean mOneShot;
     private boolean mWithBuffer;
     private boolean mFaceDetectionRunning = false;
-    private Object mAutoFocusCallbackLock = new Object();
+    private final Object mAutoFocusCallbackLock = new Object();
 
     private static final int NO_ERROR = 0;
     private static final int EACCESS = -13;
     private static final int ENODEV = -19;
+    private static final int EBUSY = -16;
+    private static final int EINVAL = -22;
+    private static final int ENOSYS = -38;
+    private static final int EUSERS = -87;
+    private static final int EOPNOTSUPP = -95;
 
     /**
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
@@ -190,6 +195,22 @@
     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
 
     /**
+     * Camera HAL device API version 1.0
+     * @hide
+     */
+    public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+
+    /**
+     * A constant meaning the normal camera connect/open will be used.
+     */
+    private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2;
+
+    /**
+     * Used to indicate HAL version un-specified.
+     */
+    private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+
+    /**
      * Hardware face detection. It does not use much CPU.
      */
     private static final int CAMERA_FACE_DETECTION_HW = 0;
@@ -331,14 +352,82 @@
         return null;
     }
 
-    Camera(int cameraId) {
-        int err = cameraInit(cameraId);
+    /**
+     * Creates a new Camera object to access a particular hardware camera with
+     * given hal API version. If the same camera is opened by other applications
+     * or the hal API version is not supported by this device, this will throw a
+     * RuntimeException.
+     * <p>
+     * You must call {@link #release()} when you are done using the camera,
+     * otherwise it will remain locked and be unavailable to other applications.
+     * <p>
+     * Your application should only have one Camera object active at a time for
+     * a particular hardware camera.
+     * <p>
+     * Callbacks from other methods are delivered to the event loop of the
+     * thread which called open(). If this thread has no event loop, then
+     * callbacks are delivered to the main application event loop. If there is
+     * no main application event loop, callbacks are not delivered.
+     * <p class="caution">
+     * <b>Caution:</b> On some devices, this method may take a long time to
+     * complete. It is best to call this method from a worker thread (possibly
+     * using {@link android.os.AsyncTask}) to avoid blocking the main
+     * application UI thread.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as.
+     * @return a new Camera object, connected, locked and ready for use.
+     *
+     * @throws IllegalArgumentException if the {@code halVersion} is invalid
+     *
+     * @throws RuntimeException if opening the camera fails (for example, if the
+     * camera is in use by another process or device policy manager has disabled
+     * the camera).
+     *
+     * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
+     * @see #CAMERA_HAL_API_VERSION_1_0
+     *
+     * @hide
+     */
+    public static Camera openLegacy(int cameraId, int halVersion) {
+        if (halVersion < CAMERA_HAL_API_VERSION_1_0) {
+            throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+        }
+
+        return new Camera(cameraId, halVersion);
+    }
+
+    /**
+     * Create a legacy camera object.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as.
+     */
+    private Camera(int cameraId, int halVersion) {
+        int err = cameraInitVersion(cameraId, halVersion);
         if (checkInitErrors(err)) {
             switch(err) {
                 case EACCESS:
                     throw new RuntimeException("Fail to connect to camera service");
                 case ENODEV:
                     throw new RuntimeException("Camera initialization failed");
+                case ENOSYS:
+                    throw new RuntimeException("Camera initialization failed because some methods"
+                            + " are not implemented");
+                case EOPNOTSUPP:
+                    throw new RuntimeException("Camera initialization failed because the hal"
+                            + " version is not supported by this device");
+                case EINVAL:
+                    throw new RuntimeException("Camera initialization failed because the input"
+                            + " arugments are invalid");
+                case EBUSY:
+                    throw new RuntimeException("Camera initialization failed because the camera"
+                            + " device was already opened");
+                case EUSERS:
+                    throw new RuntimeException("Camera initialization failed because the max"
+                            + " number of camera devices were already opened");
                 default:
                     // Should never hit this.
                     throw new RuntimeException("Unknown camera error");
@@ -346,10 +435,7 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public int cameraInit(int cameraId) {
+    private int cameraInitVersion(int cameraId, int halVersion) {
         mShutterCallback = null;
         mRawImageCallback = null;
         mJpegCallback = null;
@@ -369,9 +455,48 @@
 
         String packageName = ActivityThread.currentPackageName();
 
-        return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+        return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName);
     }
 
+    private int cameraInitNormal(int cameraId) {
+        return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT);
+    }
+
+    /**
+     * Connect to the camera service using #connectLegacy
+     *
+     * <p>
+     * This acts the same as normal except that it will return
+     * the detailed error code if open fails instead of
+     * converting everything into {@code NO_INIT}.</p>
+     *
+     * <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p>
+     *
+     * @return a detailed errno error code, or {@code NO_ERROR} on success
+     *
+     * @hide
+     */
+    public int cameraInitUnspecified(int cameraId) {
+        return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED);
+    }
+
+    /** used by Camera#open, Camera#open(int) */
+    Camera(int cameraId) {
+        int err = cameraInitNormal(cameraId);
+        if (checkInitErrors(err)) {
+            switch(err) {
+                case EACCESS:
+                    throw new RuntimeException("Fail to connect to camera service");
+                case ENODEV:
+                    throw new RuntimeException("Camera initialization failed");
+                default:
+                    // Should never hit this.
+                    throw new RuntimeException("Unknown camera error");
+            }
+        }
+    }
+
+
     /**
      * @hide
      */
@@ -392,11 +517,12 @@
     Camera() {
     }
 
+    @Override
     protected void finalize() {
         release();
     }
 
-    private native final int native_setup(Object camera_this, int cameraId,
+    private native final int native_setup(Object camera_this, int cameraId, int halVersion,
                                            String packageName);
 
     private native final void native_release();
@@ -929,7 +1055,7 @@
 
     private class EventHandler extends Handler
     {
-        private Camera mCamera;
+        private final Camera mCamera;
 
         public EventHandler(Camera c, Looper looper) {
             super(looper);
@@ -2210,6 +2336,7 @@
          * @hide
          * @deprecated
          */
+        @Deprecated
         public void dump() {
             Log.e(TAG, "dump: size=" + mMap.size());
             for (String k : mMap.keySet()) {
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 4c50dda..2bc3dd4 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -69,4 +69,16 @@
      * well-formatted in the generated java method.
      */
     int getCameraVendorTagDescriptor(out BinderHolder desc);
+
+    // Writes the camera1 parameters into a single-element array.
+    int getLegacyParameters(int cameraId, out String[] parameters);
+    // Determines if a particular API version is supported; see ICameraService.h for version defines
+    int supportsCameraApi(int cameraId, int apiVersion);
+
+    int connectLegacy(ICameraClient client, int cameraId,
+                    int halVersion,
+                    String clientPackageName,
+                    int clientUid,
+                    // Container for an ICamera object
+                    out BinderHolder device);
 }
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index c593e9e..de2cc67 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,323 @@
     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.
+     *
+     * @hide
+     * @see #isWakeUpSensor()
+     */
+    public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41;
+
+    /**
+     * A constant string describing a wake up tilt detector sensor type.
+     *
+     * @hide
+     * @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 +736,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;
@@ -427,16 +754,47 @@
      */
     public static final int TYPE_ALL = -1;
 
-    /* Reporting mode constants for sensors. Each sensor will have exactly one
-       reporting mode associated with it. */
-    // Events are reported at a constant rate.
-    static int REPORTING_MODE_CONTINUOUS = 1;
+    // If this flag is set, the sensor defined as a wake up sensor. This field and REPORTING_MODE_*
+    // constants are defined as flags in sensors.h. Modify at both places if needed.
+    private static final int SENSOR_FLAG_WAKE_UP_SENSOR = 1;
 
-    // Events are reported only when the value changes.
-    static int REPORTING_MODE_ON_CHANGE = 2;
+    /**
+     * Events are reported at a constant rate which is set by the rate parameter of
+     * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}. Note: If other
+     * applications are requesting a higher rate, the sensor data might be delivered at faster rates
+     * than requested.
+     */
+    public static final int REPORTING_MODE_CONTINUOUS = 0;
 
-    // Upon detection of an event, the sensor deactivates itself and then sends a single event.
-    static int REPORTING_MODE_ONE_SHOT = 3;
+    /**
+     * Events are reported only when the value changes. Event delivery rate can be limited by
+     * setting appropriate value for rate parameter of
+     * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} Note: If other
+     * applications are requesting a higher rate, the sensor data might be delivered at faster rates
+     * than requested.
+     */
+    public static final int REPORTING_MODE_ON_CHANGE = 1;
+
+    /**
+     * Events are reported in one-shot mode. Upon detection of an event, the sensor deactivates
+     * itself and then sends a single event. Sensors of this reporting mode must be registered to
+     * using {@link SensorManager#requestTriggerSensor(TriggerEventListener, Sensor)}.
+     */
+    public static final int REPORTING_MODE_ONE_SHOT = 2;
+
+    /**
+     * Events are reported as described in the description of the sensor. The rate passed to
+     * registerListener might not have an impact on the rate of event delivery. See the sensor
+     * definition for more information on when and how frequently the events are reported. For
+     * example, step detectors report events when a step is detected.
+     *
+     * @see SensorManager#registerListener(SensorEventListener, Sensor, int, int)
+     */
+    public static final int REPORTING_MODE_SPECIAL_TRIGGER = 3;
+
+    // Mask for the LSB 2nd, 3rd and fourth bits.
+    private static final int REPORTING_MODE_MASK = 0xE;
+    private static final int REPORTING_MODE_SHIFT = 1;
 
     // TODO(): The following arrays are fragile and error-prone. This needs to be refactored.
 
@@ -445,58 +803,74 @@
     // associated with
     // {@link SensorEvent} or {@link TriggerEvent} for the Sensor
     private static final int[] sSensorReportingModes = {
-            0, 0, // padding because sensor types start at 1
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ACCELEROMETER
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GEOMAGNETIC_FIELD
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_ORIENTATION
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GYROSCOPE
-            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_LIGHT
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_PRESSURE
-            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_TEMPERATURE
-            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_PROXIMITY
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_GRAVITY
-            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_LINEAR_ACCELERATION
-            REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_ROTATION_VECTOR
-            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_RELATIVE_HUMIDITY
-            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_AMBIENT_TEMPERATURE
-            REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED
-            REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_GAME_ROTATION_VECTOR
-            REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED
-            REPORTING_MODE_ONE_SHOT,   1, // SENSOR_TYPE_SIGNIFICANT_MOTION
-            // added post 4.3
-            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
+            0, // padding because sensor types start at 1
+            3, // SENSOR_TYPE_ACCELEROMETER
+            3, // SENSOR_TYPE_GEOMAGNETIC_FIELD
+            3, // SENSOR_TYPE_ORIENTATION
+            3, // SENSOR_TYPE_GYROSCOPE
+            3, // SENSOR_TYPE_LIGHT
+            3, // SENSOR_TYPE_PRESSURE
+            3, // SENSOR_TYPE_TEMPERATURE
+            3, // SENSOR_TYPE_PROXIMITY
+            3, // SENSOR_TYPE_GRAVITY
+            3, // SENSOR_TYPE_LINEAR_ACCELERATION
+            5, // SENSOR_TYPE_ROTATION_VECTOR
+            3, // SENSOR_TYPE_RELATIVE_HUMIDITY
+            3, // SENSOR_TYPE_AMBIENT_TEMPERATURE
+            6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED
+            4, // SENSOR_TYPE_GAME_ROTATION_VECTOR
+            6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED
+            1, // SENSOR_TYPE_SIGNIFICANT_MOTION
+            1, // SENSOR_TYPE_STEP_DETECTOR
+            1, // SENSOR_TYPE_STEP_COUNTER
+            5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR
+            1, // SENSOR_TYPE_HEART_RATE_MONITOR
+            3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+            // wake up variants of base sensors
+            3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER
+            3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD
+            3, // SENSOR_TYPE_WAKE_UP_ORIENTATION
+            3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE
+            3, // SENSOR_TYPE_WAKE_UP_LIGHT
+            3, // SENSOR_TYPE_WAKE_UP_PRESSURE
+            3, // SENSOR_TYPE_WAKE_UP_GRAVITY
+            3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION
+            5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR
+            3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY
+            3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+            6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+            4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+            6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+            1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR
+            1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER
+            5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+            1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
+            1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
+            1, // SENSOR_TYPE_WAKE_GESTURE
     };
 
-    static int getReportingMode(Sensor sensor) {
-        int offset = sensor.mType * 2;
-        if (offset >= sSensorReportingModes.length) {
-            // we don't know about this sensor, so this is probably a
-            // vendor-defined sensor, in that case, we figure out the reporting
-            // mode from the sensor meta-data.
-            int minDelay = sensor.mMinDelay;
-            if (minDelay == 0) {
-                return REPORTING_MODE_ON_CHANGE;
-            } else if (minDelay < 0) {
-                return REPORTING_MODE_ONE_SHOT;
-            } else {
-                return REPORTING_MODE_CONTINUOUS;
-            }
-        }
-        return sSensorReportingModes[offset];
+    /**
+     * Each sensor has exactly one reporting mode associated with it. This method returns the
+     * reporting mode constant for this sensor type.
+     *
+     * @return Reporting mode for the input sensor, one of REPORTING_MODE_* constants.
+     * @see #REPORTING_MODE_CONTINUOUS
+     * @see #REPORTING_MODE_ON_CHANGE
+     * @see #REPORTING_MODE_ONE_SHOT
+     * @see #REPORTING_MODE_SPECIAL_TRIGGER
+     */
+    public int getReportingMode() {
+        return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT);
     }
 
     static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
-        int type = sensor.mType;
         // RotationVector length has changed to 3 to 5 for API level 18
         // Set it to 3 for backward compatibility.
-        if (type == Sensor.TYPE_ROTATION_VECTOR &&
+        if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR &&
                 sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
             return 3;
         }
-        int offset = type * 2 + 1;
+        int offset = sensor.mType;
         if (offset >= sSensorReportingModes.length) {
             // we don't know about this sensor, so this is probably a
             // vendor-defined sensor, in that case, we don't know how many value
@@ -525,6 +899,8 @@
     private int     mFifoMaxEventCount;
     private String  mStringType;
     private String  mRequiredPermission;
+    private int     mMaxDelay;
+    private int     mFlags;
 
     Sensor() {
     }
@@ -625,6 +1001,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 (mFlags & SENSOR_FLAG_WAKE_UP_SENSOR) != 0;
+    }
+
     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..a6c3ea4 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -99,7 +99,7 @@
             return false;
         }
         // Trigger Sensors should use the requestTriggerSensor call.
-        if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+        if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
             Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
             return false;
         }
@@ -133,7 +133,7 @@
     @Override
     protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
         // Trigger Sensors should use the cancelTriggerSensor call.
-        if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+        if (sensor != null && sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
             return;
         }
 
@@ -159,7 +159,7 @@
     protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
         if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
 
-        if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false;
+        if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
 
         synchronized (mTriggerListeners) {
             TriggerEventQueue queue = mTriggerListeners.get(listener);
@@ -181,7 +181,7 @@
     @Override
     protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
             boolean disable) {
-        if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) {
+        if (sensor != null && sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) {
             return false;
         }
         synchronized (mTriggerListeners) {
@@ -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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 9eea545..14e6f92 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -466,6 +466,34 @@
             new Key<Integer>("android.control.maxRegionsAf", int.class);
 
     /**
+     * <p>List of available high speed video size and fps range configurations
+     * supported by the camera device, in the format of (width, height, fps_min, fps_max).</p>
+     * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes},
+     * this metadata will list the supported high speed video size and fps range
+     * configurations. All the sizes listed in this configuration will be a subset
+     * of the sizes reported by StreamConfigurationMap#getOutputSizes for processed
+     * non-stalling formats.</p>
+     * <p>For the high speed video use case, where the application will set
+     * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the application must
+     * select the video size and fps range from this metadata to configure the recording and
+     * preview streams and setup the recording requests. For example, if the application intends
+     * to do high speed recording, it can select the maximum size reported by this metadata to
+     * configure output streams. Once the size is selected, application can filter this metadata
+     * by selected size and get the supported fps ranges, and use these fps ranges to setup the
+     * recording requests.</p>
+     * <p>For normal video recording use case, where some application will NOT set
+     * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the fps ranges
+     * reported in this metadata must not be used to setup capture requests, or it will cause
+     * request error.</p>
+     *
+     * @see CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES
+     * @see CaptureRequest#CONTROL_SCENE_MODE
+     * @hide
+     */
+    public static final Key<int[]> CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS =
+            new Key<int[]>("android.control.availableHighSpeedVideoConfigurations", int[].class);
+
+    /**
      * <p>The set of edge enhancement modes supported by this camera device.</p>
      * <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
      * <p>Full-capability camera devices must always support OFF and FAST.</p>
@@ -579,6 +607,7 @@
      * of the lens that can be focused correctly.</p>
      * <p>If the lens is fixed-focus, this should be
      * 0.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      */
     public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE =
             new Key<Float>("android.lens.info.minimumFocusDistance", float.class);
@@ -1353,7 +1382,11 @@
     /**
      * <p>Range of valid exposure
      * times used by {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}.</p>
+     * <p>The min value will be &lt;= 100e3 (100 us). For FULL
+     * capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} == FULL),
+     * max will be &gt;= 100e6 (100ms)</p>
      *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CaptureRequest#SENSOR_EXPOSURE_TIME
      */
     public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE =
@@ -1370,7 +1403,10 @@
      * <p>Refer to
      * StreamConfigurationMap#getOutputMinFrameDuration(int,Size)
      * for the minimum frame duration values.</p>
+     * <p>For FULL capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} == FULL),
+     * max will be &gt;= 100e6 (100ms).</p>
      *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CaptureRequest#SENSOR_FRAME_DURATION
      */
     public static final Key<Long> SENSOR_INFO_MAX_FRAME_DURATION =
@@ -1423,6 +1459,18 @@
             new Key<Integer>("android.sensor.info.whiteLevel", int.class);
 
     /**
+     * <p>The sensor timestamp calibration quality.</p>
+     * <p>The sensor timestamp calibration quality determines the reliability of
+     * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} provided by the camera device.</p>
+     *
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     * @see #SENSOR_INFO_TIMESTAMP_CALIBRATION_UNCALIBRATED
+     * @see #SENSOR_INFO_TIMESTAMP_CALIBRATION_CALIBRATED
+     */
+    public static final Key<Integer> SENSOR_INFO_TIMESTAMP_CALIBRATION =
+            new Key<Integer>("android.sensor.info.timestampCalibration", int.class);
+
+    /**
      * <p>The standard reference illuminant used as the scene light source when
      * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
      * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
@@ -1735,18 +1783,26 @@
     /**
      * <p>Generally classifies the overall set of the camera device functionality.</p>
      * <p>Camera devices will come in two flavors: LIMITED and FULL.</p>
-     * <p>A FULL device has the most support possible and will enable the
-     * widest range of use cases such as:</p>
+     * <p>A FULL device has the most support possible and will support below capabilities:</p>
      * <ul>
      * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li>
      * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li>
      * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li>
      * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li>
+     * <li>Arbitrary cropping region ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>==</code> FREEFORM)</li>
+     * <li>At least 3 processed (but not stalling) format output streams ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>&gt;=</code> 3)</li>
+     * <li>The required stream configuration defined in android.scaler.availableStreamConfigurations</li>
+     * <li>The required exposure time range defined in {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
+     * <li>The required maxFrameDuration defined in {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
      * </ul>
      * <p>A LIMITED device may have some or none of the above characteristics.
      * To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
      *
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC
+     * @see CameraCharacteristics#SCALER_CROPPING_TYPE
+     * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+     * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
      * @see CameraCharacteristics#SYNC_MAX_LATENCY
      * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
      * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a69a813..9a3d806 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
+import android.hardware.CameraInfo;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.legacy.CameraDeviceUserShim;
+import android.hardware.camera2.legacy.LegacyMetadataMapper;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
 import android.hardware.camera2.utils.BinderHolder;
@@ -57,6 +59,10 @@
     private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
     private static final int USE_CALLING_UID = -1;
 
+    @SuppressWarnings("unused")
+    private static final int API_VERSION_1 = 1;
+    private static final int API_VERSION_2 = 2;
+
     private final ICameraService mCameraService;
     private ArrayList<String> mDeviceIdList;
 
@@ -84,9 +90,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 {
@@ -143,6 +148,9 @@
 
         synchronized (mLock) {
             mListenerMap.put(listener, handler);
+
+            // TODO: fire the current oldest known state when adding a new listener
+            //    (must be done while holding lock)
         }
     }
 
@@ -186,16 +194,46 @@
             }
         }
 
-        CameraMetadataNative info = new CameraMetadataNative();
-        try {
-            mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info);
-        } catch(CameraRuntimeException e) {
-            throw e.asChecked();
-        } catch(RemoteException e) {
-            // impossible
-            return null;
+        int id = Integer.valueOf(cameraId);
+
+        /*
+         * Get the camera characteristics from the camera service directly if it supports it,
+         * otherwise get them from the legacy shim instead.
+         */
+
+        if (!supportsCamera2Api(cameraId)) {
+            // Legacy backwards compatibility path; build static info from the camera parameters
+            String[] outParameters = new String[1];
+            try {
+                mCameraService.getLegacyParameters(id, /*out*/outParameters);
+                String parameters = outParameters[0];
+
+                CameraInfo info = new CameraInfo();
+                mCameraService.getCameraInfo(id, /*out*/info);
+
+                return LegacyMetadataMapper.createCharacteristics(parameters, info);
+            } catch (RemoteException e) {
+                // Impossible
+                return null;
+            } catch (CameraRuntimeException e) {
+                throw e.asChecked();
+            }
+
+        } else {
+            // Normal path: Get the camera characteristics directly from the camera service
+            CameraMetadataNative info = new CameraMetadataNative();
+
+            try {
+                mCameraService.getCameraCharacteristics(id, info);
+            } catch(CameraRuntimeException e) {
+                throw e.asChecked();
+            } catch(RemoteException e) {
+                // impossible
+                return null;
+            }
+
+            return new CameraCharacteristics(info);
         }
-        return new CameraCharacteristics(info);
     }
 
     /**
@@ -240,11 +278,20 @@
                 ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
                 int id = Integer.parseInt(cameraId);
                 try {
-                    mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
-                            USE_CALLING_UID, holder);
-                    cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+                    if (supportsCamera2Api(cameraId)) {
+                        // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
+                        mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
+                                USE_CALLING_UID, holder);
+                        cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+                    } else {
+                        // Use legacy camera implementation for HAL1 devices
+                        Log.i(TAG, "Using legacy camera HAL.");
+                        cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                    }
                 } catch (CameraRuntimeException e) {
-                    if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
+                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+                        throw new AssertionError("Should've gone down the shim path");
+                    } 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 +488,65 @@
         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());
+        }
+    }
+
+    /**
+     * Queries the camera service if it supports the camera2 api directly, or needs a shim.
+     *
+     * @param cameraId a non-{@code null} camera identifier
+     * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise.
+     */
+    private boolean supportsCamera2Api(String cameraId) {
+        return supportsCameraApi(cameraId, API_VERSION_2);
+    }
+
+    /**
+     * Queries the camera service if it supports a camera api directly, or needs a shim.
+     *
+     * @param cameraId a non-{@code null} camera identifier
+     * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2}
+     * @return {@code true} if connecting will work for that device version.
+     */
+    private boolean supportsCameraApi(String cameraId, int apiVersion) {
+        int id = Integer.parseInt(cameraId);
+
+        /*
+         * Possible return values:
+         * - NO_ERROR => Camera2 API is supported
+         * - CAMERA_DEPRECATED_HAL => Camera2 API is *not* supported (thrown as an exception)
+         *
+         * Anything else is an unexpected error we don't want to recover from.
+         */
+
+        try {
+            int res = mCameraService.supportsCameraApi(id, apiVersion);
+
+            if (res != CameraBinderDecorator.NO_ERROR) {
+                throw new AssertionError("Unexpected value " + res);
+            }
+
+            return true;
+        } catch (CameraRuntimeException e) {
+            if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+                return false;
+            } else {
+                throw e;
+            }
+        } catch (RemoteException e) {
+            throw new AssertionError("Camera service unreachable", e);
+        }
+    }
+
     // 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/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 33e1915..e464f2a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -248,7 +248,6 @@
      * <li>Manual frame duration control<ul>
      * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
      * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
-     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
      * </ul>
      * </li>
      * <li>Manual exposure control<ul>
@@ -279,6 +278,9 @@
      * result.</p>
      * <p>A given camera device may also support additional manual sensor controls,
      * but this capability only covers the above list of controls.</p>
+     * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
+     * additionally return a min frame duration that is greater than
+     * zero for each supported size-format combination.</p>
      *
      * @see CaptureRequest#BLACK_LEVEL_LOCK
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
@@ -308,7 +310,9 @@
      * <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li>
      * </ul>
      * </li>
-     * <li>Lens shading map information<ul>
+     * <li>Manual lens shading map control<ul>
+     * <li>{@link CaptureRequest#SHADING_MODE android.shading.mode}</li>
+     * <li>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode}</li>
      * <li>android.statistics.lensShadingMap</li>
      * <li>android.lens.info.shadingMapSize</li>
      * </ul>
@@ -321,6 +325,8 @@
      *
      * @see CaptureRequest#COLOR_CORRECTION_GAINS
      * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+     * @see CaptureRequest#SHADING_MODE
+     * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
      * @see CaptureRequest#TONEMAP_CURVE
      * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
      * @see CaptureRequest#TONEMAP_MODE
@@ -419,6 +425,33 @@
     public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4;
 
     //
+    // Enumeration values for CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION
+    //
+
+    /**
+     * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic,
+     * but can not be compared to timestamps from other subsystems
+     * (e.g. accelerometer, gyro etc.), or other instances of the same or different
+     * camera devices in the same system. Timestamps between streams and results for
+     * a single camera instance are comparable, and the timestamps for all buffers
+     * and the result metadata generated by a single capture are identical.</p>
+     *
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION
+     */
+    public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_UNCALIBRATED = 0;
+
+    /**
+     * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as
+     * android.os.SystemClock#elapsedRealtimeNanos(),
+     * and they can be compared to other timestamps using that base.</p>
+     *
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION
+     */
+    public static final int SENSOR_INFO_TIMESTAMP_CALIBRATION_CALIBRATED = 1;
+
+    //
     // Enumeration values for CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
     //
 
@@ -1360,6 +1393,84 @@
      */
     public static final int CONTROL_SCENE_MODE_BARCODE = 16;
 
+    /**
+     * <p>Optimized for high speed video recording (frame rate &gt;=60fps) use case.</p>
+     * <p>The supported high speed video sizes and fps ranges are specified in
+     * android.control.availableHighSpeedVideoConfigurations. To get desired
+     * output frame rates, the application is only allowed to select video size
+     * and fps range combinations listed in this static metadata. The fps range
+     * can be control via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p>
+     * <p>In this mode, the camera device will override aeMode, awbMode, and afMode to
+     * ON, ON, and CONTINUOUS_VIDEO, respectively. All post-processing block mode
+     * controls will be overridden to be FAST. Therefore, no manual control of capture
+     * and post-processing parameters is possible. All other controls operate the
+     * same as when {@link CaptureRequest#CONTROL_MODE android.control.mode} == AUTO. This means that all other
+     * android.control.* fields continue to work, such as</p>
+     * <ul>
+     * <li>{@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</li>
+     * <li>{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</li>
+     * <li>{@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li>
+     * </ul>
+     * <p>Outside of android.control.*, the following controls will work:</p>
+     * <ul>
+     * <li>{@link CaptureRequest#FLASH_MODE android.flash.mode} (automatic flash for still capture will not work since aeMode is ON)</li>
+     * <li>{@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode} (if it is supported)</li>
+     * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li>
+     * <li>{@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode}</li>
+     * </ul>
+     * <p>For high speed recording use case, the actual maximum supported frame rate may
+     * be lower than what camera can output, depending on the destination Surfaces for
+     * the image data. For example, if the destination surface is from video encoder,
+     * the application need check if the video encoder is capable of supporting the
+     * high frame rate for a given video size, or it will end up with lower recording
+     * frame rate. If the destination surface is from preview window, the preview frame
+     * rate will be bounded by the screen refresh rate.</p>
+     * <p>The camera device will only support up to 2 output high speed streams
+     * (processed non-stalling format defined in android.request.maxNumOutputStreams)
+     * in this mode. This control will be effective only if all of below conditions are true:</p>
+     * <ul>
+     * <li>The application created no more than maxNumHighSpeedStreams processed non-stalling
+     * format output streams, where maxNumHighSpeedStreams is calculated as
+     * min(2, android.request.maxNumOutputStreams[Processed (but not-stalling)]).</li>
+     * <li>The stream sizes are selected from the sizes reported by
+     * android.control.availableHighSpeedVideoConfigurations.</li>
+     * <li>No processed non-stalling or raw streams are configured.</li>
+     * </ul>
+     * <p>When above conditions are NOT satistied, the controls of this mode and
+     * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
+     * the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
+     * and the returned capture result metadata will give the fps range choosen
+     * by the camera device.</p>
+     * <p>Switching into or out of this mode may trigger some camera ISP/sensor
+     * reconfigurations, which may introduce extra latency. It is recommended that
+     * the application avoids unnecessary scene mode switch as much as possible.</p>
+     *
+     * @see CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION
+     * @see CaptureRequest#CONTROL_AE_LOCK
+     * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+     * @see CaptureRequest#CONTROL_AE_REGIONS
+     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+     * @see CaptureRequest#CONTROL_AF_REGIONS
+     * @see CaptureRequest#CONTROL_AF_TRIGGER
+     * @see CaptureRequest#CONTROL_AWB_LOCK
+     * @see CaptureRequest#CONTROL_AWB_REGIONS
+     * @see CaptureRequest#CONTROL_EFFECT_MODE
+     * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#FLASH_MODE
+     * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
+     * @see CaptureRequest#SCALER_CROP_REGION
+     * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
+     * @see CaptureRequest#CONTROL_SCENE_MODE
+     */
+    public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17;
+
     //
     // Enumeration values for CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index bf7bd37..91ff7fa 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraCharacteristics.Key;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.TypeReference;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -280,7 +281,7 @@
 
     @Override
     public int hashCode() {
-        return mSettings.hashCode();
+        return HashCodeHelpers.hashCode(mSettings, mSurfaceSet, mUserTag);
     }
 
     public static final Parcelable.Creator<CaptureRequest> CREATOR =
@@ -1010,6 +1011,7 @@
      * @see #CONTROL_SCENE_MODE_PARTY
      * @see #CONTROL_SCENE_MODE_CANDLELIGHT
      * @see #CONTROL_SCENE_MODE_BARCODE
+     * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
      */
     public static final Key<Integer> CONTROL_SCENE_MODE =
             new Key<Integer>("android.control.sceneMode", int.class);
@@ -1020,9 +1022,17 @@
      * <p>Video stabilization automatically translates and scales images from the camera
      * in order to stabilize motion between consecutive frames.</p>
      * <p>If enabled, video stabilization can modify the
-     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream
-     * stabilized</p>
+     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
+     * <p>Switching between different video stabilization modes may take several frames
+     * to initialize, the camera device will report the current mode in capture result
+     * metadata. For example, When "ON" mode is requested, the video stabilization modes
+     * in the first several capture results may still be "OFF", and it will become "ON"
+     * when the initialization is done.</p>
+     * <p>If a camera device supports both this mode and OIS ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}),
+     * turning both modes on may produce undesirable interaction, so it is recommended not to
+     * enable both at the same time.</p>
      *
+     * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON
@@ -1258,6 +1268,14 @@
      * makes use of mechanical elements to stabilize the camera
      * sensor, and thus allows for longer exposure times before
      * camera shake becomes apparent.</p>
+     * <p>Switching between different optical stabilization modes may take several
+     * frames to initialize, the camera device will report the current mode in
+     * capture result metadata. For example, When "ON" mode is requested, the
+     * optical stabilization modes in the first several capture results may still
+     * be "OFF", and it will become "ON" when the initialization is done.</p>
+     * <p>If a camera device supports both OIS and EIS ({@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode}),
+     * turning both modes on may produce undesirable interaction, so it is recommended not
+     * to enable both at the same time.</p>
      * <p>Not all devices will support OIS; see
      * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION android.lens.info.availableOpticalStabilization} for
      * available controls.</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3d17ed3..be2d960 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1581,6 +1581,7 @@
      * @see #CONTROL_SCENE_MODE_PARTY
      * @see #CONTROL_SCENE_MODE_CANDLELIGHT
      * @see #CONTROL_SCENE_MODE_BARCODE
+     * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
      */
     public static final Key<Integer> CONTROL_SCENE_MODE =
             new Key<Integer>("android.control.sceneMode", int.class);
@@ -1591,9 +1592,17 @@
      * <p>Video stabilization automatically translates and scales images from the camera
      * in order to stabilize motion between consecutive frames.</p>
      * <p>If enabled, video stabilization can modify the
-     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream
-     * stabilized</p>
+     * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
+     * <p>Switching between different video stabilization modes may take several frames
+     * to initialize, the camera device will report the current mode in capture result
+     * metadata. For example, When "ON" mode is requested, the video stabilization modes
+     * in the first several capture results may still be "OFF", and it will become "ON"
+     * when the initialization is done.</p>
+     * <p>If a camera device supports both this mode and OIS ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}),
+     * turning both modes on may produce undesirable interaction, so it is recommended not to
+     * enable both at the same time.</p>
      *
+     * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_ON
@@ -1846,6 +1855,14 @@
      * makes use of mechanical elements to stabilize the camera
      * sensor, and thus allows for longer exposure times before
      * camera shake becomes apparent.</p>
+     * <p>Switching between different optical stabilization modes may take several
+     * frames to initialize, the camera device will report the current mode in
+     * capture result metadata. For example, When "ON" mode is requested, the
+     * optical stabilization modes in the first several capture results may still
+     * be "OFF", and it will become "ON" when the initialization is done.</p>
+     * <p>If a camera device supports both OIS and EIS ({@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode}),
+     * turning both modes on may produce undesirable interaction, so it is recommended not
+     * to enable both at the same time.</p>
      * <p>Not all devices will support OIS; see
      * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION android.lens.info.availableOpticalStabilization} for
      * available controls.</p>
@@ -2114,15 +2131,22 @@
 
     /**
      * <p>Time at start of exposure of first
-     * row of the image sensor, in nanoseconds.</p>
+     * row of the image sensor active array, in nanoseconds.</p>
      * <p>The timestamps are also included in all image
      * buffers produced for the same capture, and will be identical
-     * on all the outputs. The timestamps measure time since an
-     * unspecified starting point, and are monotonically
-     * increasing.</p>
-     * <p>They can be compared with the timestamps for other captures
-     * from the same camera device, but are not guaranteed to be
-     * comparable to any other time source.</p>
+     * on all the outputs.</p>
+     * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION android.sensor.info.timestampCalibration} <code>==</code> UNCALIBRATED,
+     * the timestamps measure time since an unspecified starting point,
+     * and are monotonically increasing. They can be compared with the
+     * timestamps for other captures from the same camera device, but are
+     * not guaranteed to be comparable to any other time source.</p>
+     * <p>When {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION android.sensor.info.timestampCalibration} <code>==</code> CALIBRATED,
+     * the timestamps measure time in the same timebase as
+     * android.os.SystemClock#elapsedRealtimeNanos(), and they can be
+     * compared to other timestamps from other subsystems that are using
+     * that base.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_CALIBRATION
      */
     public static final Key<Long> SENSOR_TIMESTAMP =
             new Key<Long>("android.sensor.timestamp", long.class);
@@ -2215,6 +2239,20 @@
             new Key<Integer>("android.sensor.testPatternMode", int.class);
 
     /**
+     * <p>Duration between the start of first row exposure
+     * and the start of last row exposure.</p>
+     * <p>This is the exposure time skew (in the unit of nanosecond) between the first and
+     * last row exposure start times. The first row and the last row are the first
+     * and last rows inside of the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
+     * <p>For typical camera sensors that use rolling shutters, this is also equivalent
+     * to the frame readout time.</p>
+     *
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     */
+    public static final Key<Long> SENSOR_ROLLING_SHUTTER_SKEW =
+            new Key<Long>("android.sensor.rollingShutterSkew", long.class);
+
+    /**
      * <p>Quality of lens shading correction applied
      * to the image data.</p>
      * <p>When set to OFF mode, no lens shading correction will be applied by the
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d9f3af4..2e59eee 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -28,6 +28,8 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.CloseableLock;
+import android.hardware.camera2.utils.CloseableLock.ScopedLock;
 import android.hardware.camera2.utils.LongParcelable;
 import android.os.Handler;
 import android.os.IBinder;
@@ -57,13 +59,14 @@
     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
     private ICameraDeviceUser mRemoteDevice;
 
-    private final Object mLock = new Object();
+    private final CloseableLock mCloseLock;
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
     private final StateListener mDeviceListener;
     private volatile StateListener mSessionStateListener;
     private final Handler mDeviceHandler;
 
+    private volatile boolean mClosing = false;
     private boolean mInError = false;
     private boolean mIdle = true;
 
@@ -100,7 +103,9 @@
     private final Runnable mCallOnOpened = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onOpened(CameraDeviceImpl.this);
@@ -113,7 +118,9 @@
     private final Runnable mCallOnUnconfigured = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onUnconfigured(CameraDeviceImpl.this);
@@ -126,7 +133,9 @@
     private final Runnable mCallOnActive = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onActive(CameraDeviceImpl.this);
@@ -139,7 +148,9 @@
     private final Runnable mCallOnBusy = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onBusy(CameraDeviceImpl.this);
@@ -150,20 +161,29 @@
     };
 
     private final Runnable mCallOnClosed = new Runnable() {
+        private boolean mClosedOnce = false;
+
         @Override
         public void run() {
+            if (mClosedOnce) {
+                throw new AssertionError("Don't post #onClosed more than once");
+            }
+
             StateListener sessionListener = mSessionStateListener;
             if (sessionListener != null) {
                 sessionListener.onClosed(CameraDeviceImpl.this);
             }
             mDeviceListener.onClosed(CameraDeviceImpl.this);
+            mClosedOnce = true;
         }
     };
 
     private final Runnable mCallOnIdle = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onIdle(CameraDeviceImpl.this);
@@ -176,7 +196,9 @@
     private final Runnable mCallOnDisconnected = new Runnable() {
         @Override
         public void run() {
-            if (!CameraDeviceImpl.this.isClosed()) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 StateListener sessionListener = mSessionStateListener;
                 if (sessionListener != null) {
                     sessionListener.onDisconnected(CameraDeviceImpl.this);
@@ -195,6 +217,7 @@
         mDeviceListener = listener;
         mDeviceHandler = handler;
         mCharacteristics = characteristics;
+        mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId);
 
         final int MAX_TAG_LEN = 23;
         String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -210,8 +233,8 @@
     }
 
     public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
+        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
         // TODO: Move from decorator to direct binder-mediated exceptions
-        synchronized(mLock) {
             // If setRemoteFailure already called, do nothing
             if (mInError) return;
 
@@ -254,7 +277,9 @@
         }
         final int code = failureCode;
         final boolean isError = failureIsError;
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+            if (scopedLock == null) return; // Camera already closed, can't go to error state
+
             mInError = true;
             mDeviceHandler.post(new Runnable() {
                 @Override
@@ -280,7 +305,7 @@
         if (outputs == null) {
             outputs = new ArrayList<Surface>();
         }
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
@@ -344,7 +369,7 @@
     public void createCaptureSession(List<Surface> outputs,
             CameraCaptureSession.StateListener listener, Handler handler)
             throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             if (DEBUG) {
                 Log.d(TAG, "createCaptureSession");
             }
@@ -389,7 +414,7 @@
     @Override
     public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -435,7 +460,7 @@
      * starting and stopping repeating request and flushing.
      *
      * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
-     * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
+     * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
      * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
      * is added to the list mFrameNumberRequestPairs.</p>
      *
@@ -446,7 +471,7 @@
     private void checkEarlyTriggerSequenceComplete(
             final int requestId, final long lastFrameNumber) {
         // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
-        // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
+        // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
         if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
             final CaptureListenerHolder holder;
             int index = mCaptureListenerMap.indexOfKey(requestId);
@@ -463,7 +488,7 @@
 
             if (holder != null) {
                 if (DEBUG) {
-                    Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
+                    Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
                             + " request did not reach HAL");
                 }
 
@@ -480,10 +505,9 @@
                                     || lastFrameNumber > Integer.MAX_VALUE) {
                                 throw new AssertionError(lastFrameNumber + " cannot be cast to int");
                             }
-                            holder.getListener().onCaptureSequenceCompleted(
+                            holder.getListener().onCaptureSequenceAborted(
                                     CameraDeviceImpl.this,
-                                    requestId,
-                                    lastFrameNumber);
+                                    requestId);
                         }
                     }
                 };
@@ -509,7 +533,7 @@
             handler = checkHandler(handler);
         }
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             int requestId;
 
@@ -581,7 +605,7 @@
     @Override
     public void stopRepeating() throws CameraAccessException {
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
 
@@ -612,7 +636,7 @@
 
     private void waitUntilIdle() throws CameraAccessException {
 
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
                 throw new IllegalStateException("Active repeating request ongoing");
@@ -633,7 +657,7 @@
 
     @Override
     public void flush() throws CameraAccessException {
-        synchronized (mLock) {
+        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
             checkIfCameraClosedOrInError();
 
             mDeviceHandler.post(mCallOnBusy);
@@ -656,8 +680,15 @@
 
     @Override
     public void close() {
-        synchronized (mLock) {
+        mClosing = true;
+        // Acquire exclusive lock, close, release (idempotent)
+        mCloseLock.close();
 
+        /*
+         *  The rest of this is safe, since no other methods will be able to execute
+         *  (they will throw ISE instead; the callbacks will get dropped)
+         */
+        {
             try {
                 if (mRemoteDevice != null) {
                     mRemoteDevice.disconnect();
@@ -805,7 +836,12 @@
                 // remove request from mCaptureListenerMap
                 final int requestId = frameNumberRequestPair.getValue();
                 final CaptureListenerHolder holder;
-                synchronized (mLock) {
+                try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                    if (scopedLock == null) {
+                        Log.w(TAG, "Camera closed while checking sequences");
+                        return;
+                    }
+
                     int index = mCaptureListenerMap.indexOfKey(requestId);
                     holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
                             : null;
@@ -890,9 +926,12 @@
         @Override
         public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
             Runnable r = null;
-            if (isClosed()) return;
 
-            synchronized(mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) {
+                    return; // Camera already closed
+                }
+
                 mInError = true;
                 switch (errorCode) {
                     case ERROR_CAMERA_DISCONNECTED:
@@ -914,25 +953,24 @@
                         break;
                 }
                 CameraDeviceImpl.this.mDeviceHandler.post(r);
-            }
 
-            // Fire onCaptureSequenceCompleted
-            if (DEBUG) {
-                Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+                // Fire onCaptureSequenceCompleted
+                if (DEBUG) {
+                    Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+                }
+                mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+                checkAndFireSequenceComplete();
             }
-            mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
-            checkAndFireSequenceComplete();
-
         }
 
         @Override
         public void onCameraIdle() {
-            if (isClosed()) return;
-
             if (DEBUG) {
                 Log.d(TAG, "Camera now idle");
             }
-            synchronized (mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
                 if (!CameraDeviceImpl.this.mIdle) {
                     CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
                 }
@@ -948,30 +986,33 @@
             }
             final CaptureListenerHolder holder;
 
-            // Get the listener for this frame ID, if there is one
-            synchronized (mLock) {
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
+
+                // Get the listener for this frame ID, if there is one
                 holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
-            }
 
-            if (holder == null) {
-                return;
-            }
+                if (holder == null) {
+                    return;
+                }
 
-            if (isClosed()) return;
+                if (isClosed()) return;
 
-            // Dispatch capture start notice
-            holder.getHandler().post(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()) {
-                            holder.getListener().onCaptureStarted(
-                                CameraDeviceImpl.this,
-                                holder.getRequest(resultExtras.getSubsequenceId()),
-                                timestamp);
+                // Dispatch capture start notice
+                holder.getHandler().post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()) {
+                                holder.getListener().onCaptureStarted(
+                                    CameraDeviceImpl.this,
+                                    holder.getRequest(resultExtras.getSubsequenceId()),
+                                    timestamp);
+                            }
                         }
-                    }
-                });
+                    });
+
+            }
         }
 
         @Override
@@ -984,88 +1025,91 @@
                         + requestId);
             }
 
+            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+                if (scopedLock == null) return; // Camera already closed
 
-            // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
-            result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
-                    getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+                // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+                result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+                        getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
 
-            final CaptureListenerHolder holder;
-            synchronized (mLock) {
-                holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
-            }
+                final CaptureListenerHolder holder =
+                        CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
 
-            Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
-            boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+                Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+                boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
 
-            // Update tracker (increment counter) when it's not a partial result.
-            if (!quirkIsPartialResult) {
-                mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
-            }
-
-            // Check if we have a listener for this
-            if (holder == null) {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "holder is null, early return at frame "
-                                    + resultExtras.getFrameNumber());
+                // Update tracker (increment counter) when it's not a partial result.
+                if (!quirkIsPartialResult) {
+                    mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(),
+                            /*error*/false);
                 }
-                return;
-            }
 
-            if (isClosed()) {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "camera is closed, early return at frame "
-                                    + resultExtras.getFrameNumber());
+                // Check if we have a listener for this
+                if (holder == null) {
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                "holder is null, early return at frame "
+                                        + resultExtras.getFrameNumber());
+                    }
+                    return;
                 }
-                return;
-            }
 
-            final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
-
-
-            Runnable resultDispatch = null;
-
-            // Either send a partial result or the final capture completed result
-            if (quirkIsPartialResult) {
-                final CaptureResult resultAsCapture =
-                        new CaptureResult(result, request, requestId);
-
-                // Partial result
-                resultDispatch = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()){
-                            holder.getListener().onCapturePartial(
-                                CameraDeviceImpl.this,
-                                request,
-                                resultAsCapture);
-                        }
+                if (isClosed()) {
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                "camera is closed, early return at frame "
+                                        + resultExtras.getFrameNumber());
                     }
-                };
-            } else {
-                final TotalCaptureResult resultAsCapture =
-                        new TotalCaptureResult(result, request, requestId);
+                    return;
+                }
 
-                // Final capture result
-                resultDispatch = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()){
-                            holder.getListener().onCaptureCompleted(
-                                CameraDeviceImpl.this,
-                                request,
-                                resultAsCapture);
+                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+
+
+                Runnable resultDispatch = null;
+
+                // Either send a partial result or the final capture completed result
+                if (quirkIsPartialResult) {
+                    final CaptureResult resultAsCapture =
+                            new CaptureResult(result, request, requestId);
+
+                    // Partial result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()){
+                                holder.getListener().onCapturePartial(
+                                    CameraDeviceImpl.this,
+                                    request,
+                                    resultAsCapture);
+                            }
                         }
-                    }
-                };
-            }
+                    };
+                } else {
+                    final TotalCaptureResult resultAsCapture =
+                            new TotalCaptureResult(result, request, requestId);
 
-            holder.getHandler().post(resultDispatch);
+                    // Final capture result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!CameraDeviceImpl.this.isClosed()){
+                                holder.getListener().onCaptureCompleted(
+                                    CameraDeviceImpl.this,
+                                    request,
+                                    resultAsCapture);
+                            }
+                        }
+                    };
+                }
 
-            // Fire onCaptureSequenceCompleted
-            if (!quirkIsPartialResult) {
-                checkAndFireSequenceComplete();
+                holder.getHandler().post(resultDispatch);
+
+                // Fire onCaptureSequenceCompleted
+                if (!quirkIsPartialResult) {
+                    checkAndFireSequenceComplete();
+                }
+
             }
         }
 
@@ -1101,10 +1145,9 @@
         }
     }
 
+    /** Whether the camera device has started to close (may not yet have finished) */
     private boolean isClosed() {
-        synchronized(mLock) {
-            return (mRemoteDevice == null);
-        }
+        return mClosing;
     }
 
     private CameraCharacteristics getCharacteristics() {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index 22ff9c6..ab7e844 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -34,6 +34,7 @@
  *      <li>{@code CONFIGURING -> IDLE}</li>
  *      <li>{@code IDLE -> CONFIGURING}</li>
  *      <li>{@code IDLE -> CAPTURING}</li>
+ *      <li>{@code IDLE -> IDLE}</li>
  *      <li>{@code CAPTURING -> IDLE}</li>
  *      <li>{@code ANY -> ERROR}</li>
  * </ul>
@@ -216,12 +217,17 @@
                 mCurrentState = STATE_CONFIGURING;
                 break;
             case STATE_IDLE:
+                if (mCurrentState == STATE_IDLE) {
+                    break;
+                }
+
                 if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) {
                     Log.e(TAG, "Cannot call idle while in state: " + mCurrentState);
                     mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
                     doStateTransition(STATE_ERROR);
                     break;
                 }
+
                 if (mCurrentState != STATE_IDLE && mCurrentHandler != null &&
                         mCurrentListener != null) {
                     mCurrentHandler.post(new Runnable() {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 54d9c3c..03dd354 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -17,7 +17,9 @@
 package android.hardware.camera2.legacy;
 
 import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
 import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
@@ -25,7 +27,9 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
+import android.os.ConditionVariable;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
@@ -33,7 +37,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Compatibility implementation of the Camera2 API binder interface.
@@ -53,6 +56,7 @@
     private static final String TAG = "CameraDeviceUserShim";
 
     private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+    private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
 
     private final LegacyCameraDevice mLegacyDevice;
 
@@ -60,31 +64,143 @@
     private int mSurfaceIdCounter;
     private boolean mConfiguring;
     private final SparseArray<Surface> mSurfaces;
+    private final CameraCharacteristics mCameraCharacteristics;
+    private final CameraLooper mCameraInit;
 
-    protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
+    protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
+            CameraCharacteristics characteristics, CameraLooper cameraInit) {
         mLegacyDevice = legacyCamera;
         mConfiguring = false;
         mSurfaces = new SparseArray<Surface>();
+        mCameraCharacteristics = characteristics;
+        mCameraInit = cameraInit;
 
         mSurfaceIdCounter = 0;
     }
 
+    /**
+     * Create a separate looper/thread for the camera to run on; open the camera.
+     *
+     * <p>Since the camera automatically latches on to the current thread's looper,
+     * it's important that we have our own thread with our own looper to guarantee
+     * that the camera callbacks get correctly posted to our own thread.</p>
+     */
+    private static class CameraLooper implements Runnable, AutoCloseable {
+        private final int mCameraId;
+        private Looper mLooper;
+        private volatile int mInitErrors;
+        private final Camera mCamera = Camera.openUninitialized();
+        private final ConditionVariable mStartDone = new ConditionVariable();
+        private final Thread mThread;
+
+        /**
+         * Spin up a new thread, immediately open the camera in the background.
+         *
+         * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
+         *
+         * @param cameraId numeric camera Id
+         *
+         * @see #waitForOpen
+         */
+        public CameraLooper(int cameraId) {
+            mCameraId = cameraId;
+
+            mThread = new Thread(this);
+            mThread.start();
+        }
+
+        public Camera getCamera() {
+            return mCamera;
+        }
+
+        @Override
+        public void run() {
+            // Set up a looper to be used by camera.
+            Looper.prepare();
+
+            // Save the looper so that we can terminate this thread
+            // after we are done with it.
+            mLooper = Looper.myLooper();
+            mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
+
+            mStartDone.open();
+            Looper.loop();  // Blocks forever until #close is called.
+        }
+
+        /**
+         * Quit the looper safely; then join until the thread shuts down.
+         */
+        @Override
+        public void close() {
+            if (mLooper == null) {
+                return;
+            }
+
+            mLooper.quitSafely();
+            try {
+                mThread.join();
+            } catch (InterruptedException e) {
+                throw new AssertionError(e);
+            }
+
+            mLooper = null;
+        }
+
+        /**
+         * Block until the camera opens; then return its initialization error code (if any).
+         *
+         * @param timeoutMs timeout in milliseconds
+         *
+         * @return int error code
+         *
+         * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR})
+         */
+        public int waitForOpen(int timeoutMs) {
+            // Block until the camera is open asynchronously
+            if (!mStartDone.block(timeoutMs)) {
+                Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
+                        + OPEN_CAMERA_TIMEOUT_MS + " ms");
+                try {
+                    mCamera.release();
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
+                }
+
+                throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
+            }
+
+            return mInitErrors;
+        }
+    }
+
     public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
                                                          int cameraId) {
         if (DEBUG) {
             Log.d(TAG, "Opening shim Camera device");
         }
-        // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
-        Camera legacyCamera = Camera.openUninitialized();
-        int initErrors = legacyCamera.cameraInit(cameraId);
+
+        /*
+         * Put the camera open on a separate thread with its own looper; otherwise
+         * if the main thread is used then the callbacks might never get delivered
+         * (e.g. in CTS which run its own default looper only after tests)
+         */
+
+        CameraLooper init = new CameraLooper(cameraId);
+
+        // TODO: Make this async instead of blocking
+        int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
+        Camera legacyCamera = init.getCamera();
+
         // Check errors old HAL initialization
-        if (Camera.checkInitErrors(initErrors)) {
-            // TODO: Map over old camera error codes.  This likely involves improving the error
-            // reporting in the HAL1 connect path.
-            throw new CameraRuntimeException(CameraAccessException.CAMERA_DISCONNECTED);
-        }
+        CameraBinderDecorator.throwOnError(initErrors);
+
+        CameraInfo info = new CameraInfo();
+        Camera.getCameraInfo(cameraId, info);
+
+        CameraCharacteristics characteristics =
+                LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info);
         LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
-        return new CameraDeviceUserShim(cameraId, device);
+        return new CameraDeviceUserShim(cameraId, device, characteristics, init);
     }
 
     @Override
@@ -92,7 +208,12 @@
         if (DEBUG) {
             Log.d(TAG, "disconnect called.");
         }
-        mLegacyDevice.close();
+
+        try {
+            mLegacyDevice.close();
+        } finally {
+            mCameraInit.close();
+        }
     }
 
     @Override
@@ -169,7 +290,7 @@
             }
             int numSurfaces = mSurfaces.size();
             if (numSurfaces > 0) {
-                surfaces = new ArrayList<Surface>();
+                surfaces = new ArrayList<>();
                 for (int i = 0; i < numSurfaces; ++i) {
                     surfaces.add(mSurfaces.valueAt(i));
                 }
@@ -220,8 +341,17 @@
         if (DEBUG) {
             Log.d(TAG, "createDefaultRequest called.");
         }
-        // TODO: implement createDefaultRequest.
-        Log.e(TAG, "createDefaultRequest unimplemented.");
+
+        CameraMetadataNative template;
+        try {
+            template =
+                    LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "createDefaultRequest - invalid templateId specified");
+            return CameraBinderDecorator.BAD_VALUE;
+        }
+
+        request.swap(template);
         return CameraBinderDecorator.NO_ERROR;
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 3fd2309..5d44fd2 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -100,6 +100,7 @@
                     break;
                 case MSG_ALLOW_FRAMES:
                     mDroppingFrames = false;
+                    break;
                 default:
                     Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
                     break;
@@ -148,6 +149,12 @@
         Handler handler = mGLHandlerThread.getHandler();
         handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
         mGLHandlerThread.quitSafely();
+        try {
+            mGLHandlerThread.join();
+        } catch (InterruptedException e) {
+            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+                    mGLHandlerThread.getName(), mGLHandlerThread.getId()));
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index f9cf905..4b39092 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,18 +23,21 @@
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
+import static android.hardware.camera2.utils.CameraBinderDecorator.*;
+import static com.android.internal.util.Preconditions.*;
 
 /**
  * This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -48,18 +51,20 @@
  */
 public class LegacyCameraDevice implements AutoCloseable {
     public static final String DEBUG_PROP = "HAL1ShimLogging";
-
     private final String TAG;
 
+    private static final boolean DEBUG = false;
     private final int mCameraId;
     private final ICameraDeviceCallbacks mDeviceCallbacks;
     private final CameraDeviceState mDeviceState = new CameraDeviceState();
+    private List<Surface> mConfiguredSurfaces;
 
     private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
-    private final AtomicInteger mRequestIdCounter = new AtomicInteger(0);
 
-    private final HandlerThread mCallbackHandlerThread = new HandlerThread("ResultThread");
+    private final HandlerThread mResultThread = new HandlerThread("ResultThread");
+    private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
     private final Handler mCallbackHandler;
+    private final Handler mResultHandler;
     private static final int ILLEGAL_VALUE = -1;
 
     private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
@@ -81,66 +86,108 @@
         public void onError(final int errorCode, RequestHolder holder) {
             mIdle.open();
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
-            try {
-                mDeviceCallbacks.onCameraError(errorCode, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
-            }
-
+            mResultHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onError callback.");
+                    }
+                    try {
+                        mDeviceCallbacks.onCameraError(errorCode, extras);
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Received remote exception during onCameraError callback: ", e);
+                    }
+                }
+            });
         }
 
         @Override
         public void onConfiguring() {
             // Do nothing
+            if (DEBUG) {
+                Log.d(TAG, "doing onConfiguring callback.");
+            }
         }
 
         @Override
         public void onIdle() {
             mIdle.open();
 
-            try {
-                mDeviceCallbacks.onCameraIdle();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Received remote exception during onCameraIdle callback: ", e);
-            }
+            mResultHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onIdle callback.");
+                    }
+                    try {
+                        mDeviceCallbacks.onCameraIdle();
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Received remote exception during onCameraIdle callback: ", e);
+                    }
+                }
+            });
         }
 
         @Override
         public void onCaptureStarted(RequestHolder holder) {
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
 
-            try {
-                // TODO: Don't fake timestamp
-                mDeviceCallbacks.onCaptureStarted(extras, System.nanoTime());
-            } catch (RemoteException e) {
-                Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
-            }
-
+            final long timestamp = System.nanoTime();
+            mResultHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onCaptureStarted callback.");
+                    }
+                    try {
+                        // TODO: Don't fake timestamp
+                        mDeviceCallbacks.onCaptureStarted(extras, timestamp);
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Received remote exception during onCameraError callback: ", e);
+                    }
+                }
+            });
         }
 
         @Override
-        public void onCaptureResult(CameraMetadataNative result, RequestHolder holder) {
+        public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
 
-            try {
-                // TODO: Don't fake metadata
-                mDeviceCallbacks.onResultReceived(result, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Received remote exception during onCameraError callback: ", e);
-            }
+            mResultHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "doing onCaptureResult callback.");
+                    }
+                    try {
+                        // TODO: Don't fake metadata
+                        mDeviceCallbacks.onResultReceived(result, extras);
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Received remote exception during onCameraError callback: ", e);
+                    }
+                }
+            });
         }
     };
 
     private final RequestThreadManager mRequestThreadManager;
 
     /**
-     * Check if a given surface uses {@link ImageFormat#YUV_420_888} format.
+     * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
+     * converted to this; YV12 and NV21 are the two currently supported formats.
      *
      * @param s the surface to check.
-     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888}.
+     * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
+     *          format.
      */
-    static boolean needsConversion(Surface s) {
-        return LegacyCameraDevice.nativeDetectSurfaceType(s) == ImageFormat.YUV_420_888;
+    static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
+        int nativeType = detectSurfaceType(s);
+        return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
+                nativeType == ImageFormat.NV21;
     }
 
     /**
@@ -161,6 +208,8 @@
         mDeviceCallbacks = callbacks;
         TAG = String.format("CameraDevice-%d-LE", mCameraId);
 
+        mResultThread.start();
+        mResultHandler = new Handler(mResultThread.getLooper());
         mCallbackHandlerThread.start();
         mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
         mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
@@ -172,16 +221,35 @@
     /**
      * Configure the device with a set of output surfaces.
      *
+     * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
+     *
+     * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
+     *
      * @param outputs a list of surfaces to set.
-     * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
+     * @return an error code for this binder operation, or {@link NO_ERROR}
      *          on success.
      */
     public int configureOutputs(List<Surface> outputs) {
+        if (outputs != null) {
+            for (Surface output : outputs) {
+                if (output == null) {
+                    Log.e(TAG, "configureOutputs - null outputs are not allowed");
+                    return BAD_VALUE;
+                }
+            }
+        }
+
         int error = mDeviceState.setConfiguring();
-        if (error == CameraBinderDecorator.NO_ERROR) {
+        if (error == NO_ERROR) {
             mRequestThreadManager.configure(outputs);
             error = mDeviceState.setIdle();
         }
+
+        // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
+        if (error == NO_ERROR) {
+            mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
+        }
+
         return error;
     }
 
@@ -198,7 +266,35 @@
      */
     public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
             /*out*/LongParcelable frameNumber) {
-        // TODO: validate request here
+        if (requestList == null || requestList.isEmpty()) {
+            Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
+            return BAD_VALUE;
+        }
+
+        // Make sure that there all requests have at least 1 surface; all surfaces are non-null
+        for (CaptureRequest request : requestList) {
+            if (request.getTargets().isEmpty()) {
+                Log.e(TAG, "submitRequestList - "
+                        + "Each request must have at least one Surface target");
+                return BAD_VALUE;
+            }
+
+            for (Surface surface : request.getTargets()) {
+                if (surface == null) {
+                    Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
+                    return BAD_VALUE;
+                } else if (mConfiguredSurfaces == null) {
+                    Log.e(TAG, "submitRequestList - must configure " +
+                            " device with valid surfaces before submitting requests");
+                    return INVALID_OPERATION;
+                } else if (!mConfiguredSurfaces.contains(surface)) {
+                    Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
+                    return BAD_VALUE;
+                }
+            }
+        }
+
+        // TODO: further validation of request here
         mIdle.close();
         return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
                 frameNumber);
@@ -244,6 +340,22 @@
     public void close() {
         mRequestThreadManager.quit();
         mCallbackHandlerThread.quitSafely();
+        mResultThread.quitSafely();
+
+        try {
+            mCallbackHandlerThread.join();
+        } catch (InterruptedException e) {
+            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+                    mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
+        }
+
+        try {
+            mResultThread.join();
+        } catch (InterruptedException e) {
+            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+                    mResultThread.getName(), mResultThread.getId()));
+        }
+
         // TODO: throw IllegalStateException in every method after close has been called
     }
 
@@ -258,18 +370,79 @@
         }
     }
 
-    protected static native int nativeDetectSurfaceType(Surface surface);
+    /**
+     * Query the surface for its currently configured default buffer size.
+     * @param surface a non-{@code null} {@code Surface}
+     * @return the width and height of the surface
+     *
+     * @throws NullPointerException if the {@code surface} was {@code null}
+     * @throws IllegalStateException if the {@code surface} was invalid
+     */
+    static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
+        checkNotNull(surface);
 
-    protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
+        int[] dimens = new int[2];
+        LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
 
-    protected static native void nativeConfigureSurface(Surface surface, int width, int height,
+        return new Size(dimens[0], dimens[1]);
+    }
+
+    static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
+    }
+
+    static void configureSurface(Surface surface, int width, int height,
+                                 int pixelFormat) throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        checkArgumentPositive(width, "width must be positive.");
+        checkArgumentPositive(height, "height must be positive.");
+
+        LegacyExceptionUtils.throwOnError(nativeConfigureSurface(surface, width, height,
+                pixelFormat));
+    }
+
+    static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
+                             int height, int pixelFormat)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        checkNotNull(pixelBuffer);
+        checkArgumentPositive(width, "width must be positive.");
+        checkArgumentPositive(height, "height must be positive.");
+
+        LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
+                pixelFormat));
+    }
+
+    static void setSurfaceFormat(Surface surface, int pixelFormat)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+
+        LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
+    }
+
+    static void setSurfaceDimens(Surface surface, int width, int height)
+            throws BufferQueueAbandonedException {
+        checkNotNull(surface);
+        checkArgumentPositive(width, "width must be positive.");
+        checkArgumentPositive(height, "height must be positive.");
+
+        LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
+    }
+
+    private static native int nativeDetectSurfaceType(Surface surface);
+
+    private static native int nativeDetectSurfaceDimens(Surface surface,
+            /*out*/int[/*2*/] dimens);
+
+    private static native int nativeConfigureSurface(Surface surface, int width, int height,
                                                         int pixelFormat);
 
-    protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
+    private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
                                                     int height, int pixelFormat);
 
-    protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
+    private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
 
-    protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
+    private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
 
 }
diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
new file mode 100644
index 0000000..7e0c01b
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.camera2.legacy;
+
+import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.util.AndroidException;
+
+/**
+ * Utility class containing exception handling used solely by the compatibility mode shim.
+ */
+public class LegacyExceptionUtils {
+    private static final String TAG = "LegacyExceptionUtils";
+
+    /**
+     * Checked exception thrown when a BufferQueue has been abandoned by its consumer.
+     */
+    public static class BufferQueueAbandonedException extends AndroidException {
+        public BufferQueueAbandonedException () {}
+
+        public BufferQueueAbandonedException(String name) {
+            super(name);
+        }
+
+        public BufferQueueAbandonedException(String name, Throwable cause) {
+            super(name, cause);
+        }
+
+        public BufferQueueAbandonedException(Exception cause) {
+            super(cause);
+        }
+    }
+
+    /**
+     * Throw error codes used by legacy device methods as exceptions.
+     *
+     * <p>Non-negative return values are passed through, negative return values are thrown as
+     * exceptions.</p>
+     *
+     * @param errorFlag error to throw as an exception.
+     * @throws {@link BufferQueueAbandonedException} for {@link CameraBinderDecorator#ENODEV}.
+     * @throws {@link UnsupportedOperationException} for an unknown negative error code.
+     * @return {@code errorFlag} if the value was non-negative, throws otherwise.
+     */
+    public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
+        switch (errorFlag) {
+            case CameraBinderDecorator.NO_ERROR: {
+                return CameraBinderDecorator.NO_ERROR;
+            }
+            case CameraBinderDecorator.ENODEV: {
+                throw new BufferQueueAbandonedException();
+            }
+        }
+
+        if (errorFlag < 0) {
+            throw new UnsupportedOperationException("Unknown error " + errorFlag);
+        }
+        return errorFlag;
+    }
+
+    private LegacyExceptionUtils() {
+        throw new AssertionError();
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
new file mode 100644
index 0000000..4797e20
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -0,0 +1,731 @@
+/*
+ * 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.camera2.legacy;
+
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.params.StreamConfiguration;
+import android.hardware.camera2.params.StreamConfigurationDuration;
+import android.util.Log;
+import android.util.Range;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static com.android.internal.util.Preconditions.*;
+import static android.hardware.camera2.CameraCharacteristics.*;
+
+/**
+ * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the
+ * camera characteristics.
+ */
+public class LegacyMetadataMapper {
+    private static final String TAG = "LegacyMetadataMapper";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final long NS_PER_MS = 1000000;
+
+    // from graphics.h
+    private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
+    private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+
+    // for metadata
+    private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
+
+    private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW = 0; // no raw support
+    private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC = 3; // preview, video, cb
+    private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL = 1; // 1 jpeg only
+    private static final int REQUEST_MAX_NUM_INPUT_STREAMS_COUNT = 0; // no reprocessing
+
+    /** Assume 3 HAL1 stages: Exposure, Read-out, Post-Processing */
+    private static final int REQUEST_PIPELINE_MAX_DEPTH_HAL1 = 3;
+    /** Assume 3 shim stages: Preview input, Split output, Format conversion for output */
+    private static final int REQUEST_PIPELINE_MAX_DEPTH_OURS = 3;
+    /* TODO: Update above maxDepth values once we do more performance measurements */
+
+    // For approximating JPEG stall durations
+    private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // 200 milliseconds
+    private static final long APPROXIMATE_SENSOR_AREA_PX = (1 << 23); // 8 megapixels
+    private static final long APPROXIMATE_JPEG_ENCODE_TIME_MS = 600; // 600 milliseconds
+
+    /*
+     * Development hijinks: Lie about not supporting certain capabilities
+     *
+     * - Unblock some CTS tests from running whose main intent is not the metadata itself
+     *
+     * TODO: Remove these constants and strip out any code that previously relied on them
+     * being set to true.
+     */
+    private static final boolean LIE_ABOUT_FLASH = true;
+    private static final boolean LIE_ABOUT_AE = true;
+    private static final boolean LIE_ABOUT_AF = true;
+    private static final boolean LIE_ABOUT_AWB = true;
+
+    /**
+     * Create characteristics for a legacy device by mapping the {@code parameters}
+     * and {@code info}
+     *
+     * @param parameters A non-{@code null} parameters set
+     * @param info Camera info with camera facing direction and angle of orientation
+     *
+     * @return static camera characteristics for a camera device
+     *
+     * @throws NullPointerException if any of the args were {@code null}
+     */
+    public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
+            CameraInfo info) {
+        checkNotNull(parameters, "parameters must not be null");
+        checkNotNull(info, "info must not be null");
+
+        String paramStr = parameters.flatten();
+        android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
+        outerInfo.info = info;
+
+        return createCharacteristics(paramStr, outerInfo);
+    }
+
+    /**
+     * Create characteristics for a legacy device by mapping the {@code parameters}
+     * and {@code info}
+     *
+     * @param parameters A string parseable by {@link Camera.Parameters#unflatten}
+     * @param info Camera info with camera facing direction and angle of orientation
+     * @return static camera characteristics for a camera device
+     *
+     * @throws NullPointerException if any of the args were {@code null}
+     */
+    public static CameraCharacteristics createCharacteristics(String parameters,
+            android.hardware.CameraInfo info) {
+        checkNotNull(parameters, "parameters must not be null");
+        checkNotNull(info, "info must not be null");
+        checkNotNull(info.info, "info.info must not be null");
+
+        CameraMetadataNative m = new CameraMetadataNative();
+
+        mapCharacteristicsFromInfo(m, info.info);
+
+        Camera.Parameters params = Camera.getEmptyParameters();
+        params.unflatten(parameters);
+        mapCharacteristicsFromParameters(m, params);
+
+        if (VERBOSE) {
+            Log.v(TAG, "createCharacteristics metadata:");
+            Log.v(TAG, "--------------------------------------------------- (start)");
+            m.dumpToLog();
+            Log.v(TAG, "--------------------------------------------------- (end)");
+        }
+
+        return new CameraCharacteristics(m);
+    }
+
+    private static void mapCharacteristicsFromInfo(CameraMetadataNative m, CameraInfo i) {
+        m.set(LENS_FACING, i.facing == CameraInfo.CAMERA_FACING_BACK ?
+                LENS_FACING_BACK : LENS_FACING_FRONT);
+        m.set(SENSOR_ORIENTATION, i.orientation);
+    }
+
+    private static void mapCharacteristicsFromParameters(CameraMetadataNative m,
+            Camera.Parameters p) {
+        /*
+         * info.supportedHardwareLevel
+         */
+        m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
+        mapStreamConfigs(m, p);
+        /*
+         * control.ae*
+         */
+        mapControlAe(m, p);
+        /*
+         * control.awb*
+         */
+        mapControlAwb(m, p);
+        /*
+         * control.*
+         * - Anything that doesn't have a set of related fields
+         */
+        mapControlOther(m, p);
+        /*
+         * lens.*
+         */
+        mapLens(m, p);
+        /*
+         * flash.*
+         */
+        mapFlash(m, p);
+
+        /*
+         * request.*
+         */
+        mapRequest(m, p);
+        // TODO: map other fields
+
+        /*
+         * sync.*
+         */
+        mapSync(m, p);
+    }
+
+    private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
+
+        ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
+        /*
+         * Implementation-defined (preview, recording, etc) -> use camera1 preview sizes
+         * YUV_420_888 cpu callbacks -> use camera1 preview sizes
+         * Other preview callbacks (CPU) -> use camera1 preview sizes
+         * JPEG still capture -> use camera1 still capture sizes
+         *
+         * Use platform-internal format constants here, since StreamConfigurationMap does the
+         * remapping to public format constants.
+         */
+        List<Size> previewSizes = p.getSupportedPreviewSizes();
+        appendStreamConfig(availableStreamConfigs,
+                HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, previewSizes);
+        appendStreamConfig(availableStreamConfigs,
+                ImageFormat.YUV_420_888, previewSizes);
+        for (int format : p.getSupportedPreviewFormats()) {
+            if (ImageFormat.isPublicFormat(format)) {
+                appendStreamConfig(availableStreamConfigs, format, previewSizes);
+            } else {
+                /*
+                 *  Do not add any formats unknown to us
+                 * (since it would fail runtime checks in StreamConfigurationMap)
+                 */
+                Log.w(TAG,
+                        String.format("mapStreamConfigs - Skipping non-public format %x", format));
+            }
+        }
+
+        List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
+        appendStreamConfig(availableStreamConfigs,
+                HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
+        /*
+         * scaler.availableStreamConfigurations
+         */
+        m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+                availableStreamConfigs.toArray(new StreamConfiguration[0]));
+
+        /*
+         * scaler.availableMinFrameDurations
+         */
+        // No frame durations available
+        m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
+
+        StreamConfigurationDuration[] jpegStalls =
+                new StreamConfigurationDuration[jpegSizes.size()];
+        int i = 0;
+        long longestStallDuration = -1;
+        for (Camera.Size s : jpegSizes) {
+            long stallDuration =  calculateJpegStallDuration(s);
+            jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
+                    s.height, stallDuration);
+            if (longestStallDuration < stallDuration) {
+                longestStallDuration = stallDuration;
+            }
+        }
+        /*
+         * scaler.availableStallDurations
+         */
+        // Set stall durations for jpeg, other formats use default stall duration
+        m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
+
+        /*
+         * sensor.info.maxFrameDuration
+         */
+        m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
+        /*
+         * control.aeAvailableTargetFpsRanges
+         */
+        List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
+        if (fpsRanges == null) {
+            throw new AssertionError("Supported FPS ranges cannot be null.");
+        }
+        int rangesSize = fpsRanges.size();
+        if (rangesSize <= 0) {
+            throw new AssertionError("At least one FPS range must be supported.");
+        }
+        Range<Integer>[] ranges = new Range[rangesSize];
+        int i = 0;
+        for (int[] r : fpsRanges) {
+            ranges[i++] = Range.create(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                    r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+        }
+        m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+
+        /*
+         * control.aeAvailableAntiBandingModes
+         */
+
+        List<String> antiBandingModes = p.getSupportedAntibanding();
+        int antiBandingModesSize = antiBandingModes.size();
+        if (antiBandingModesSize > 0) {
+            int[] modes = new int[antiBandingModesSize];
+            int j = 0;
+            for (String mode : antiBandingModes) {
+                int convertedMode = convertAntiBandingMode(mode);
+                if (convertedMode == -1) {
+                    Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
+                            " not supported, skipping...");
+                } else {
+                    modes[j++] = convertedMode;
+                }
+            }
+            m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
+        }
+
+        /*
+         * control.aeAvailableModes
+         */
+        List<String> flashModes = p.getSupportedFlashModes();
+
+        String[] flashModeStrings = new String[] {
+                Camera.Parameters.FLASH_MODE_AUTO,
+                Camera.Parameters.FLASH_MODE_ON,
+                Camera.Parameters.FLASH_MODE_RED_EYE,
+                // Map these manually
+                Camera.Parameters.FLASH_MODE_TORCH,
+                Camera.Parameters.FLASH_MODE_OFF,
+        };
+        int[] flashModeInts = new int[] {
+                CONTROL_AE_MODE_ON,
+                CONTROL_AE_MODE_ON_AUTO_FLASH,
+                CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+        };
+        int[] aeAvail = convertStringListToIntArray(flashModes, flashModeStrings, flashModeInts);
+
+        // No flash control -> AE is always on
+        if (aeAvail == null || aeAvail.length == 0) {
+            aeAvail = new int[] {
+                    CONTROL_AE_MODE_ON
+            };
+        }
+
+        if (LIE_ABOUT_FLASH) {
+            // TODO: Remove this branch
+            Log.w(TAG, "mapControlAe - lying; saying we only support CONTROL_AE_MODE_ON");
+            aeAvail = new int[] {
+                    CONTROL_AE_MODE_ON
+            };
+        }
+
+        m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
+    }
+
+    private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
+        if (!LIE_ABOUT_AWB) {
+            throw new AssertionError("Not implemented yet");
+        }
+    }
+
+    private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
+        /*
+         * android.control.maxRegions
+         */
+        int[] maxRegions = new int[3];
+        maxRegions[0] = p.getMaxNumMeteringAreas();
+        maxRegions[1] = 0; // AWB regions not supported in API1
+        maxRegions[2] = p.getMaxNumFocusAreas();
+        m.set(CONTROL_MAX_REGIONS, maxRegions);
+        // TODO
+    }
+
+    private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
+        /*
+         *  We can tell if the lens is fixed focus;
+         *  but if it's not, we can't tell the minimum focus distance, so leave it null then.
+         */
+        if (p.getFocusMode() == Camera.Parameters.FOCUS_MODE_FIXED) {
+            /*
+             * lens.info.minimumFocusDistance
+             */
+            m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
+        }
+    }
+
+    private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) {
+        boolean flashAvailable = false;
+        List<String> supportedFlashModes = p.getSupportedFlashModes();
+        if (supportedFlashModes != null) {
+            // If only 'OFF' is available, we don't really have flash support
+            if (!(supportedFlashModes.contains(Camera.Parameters.FLASH_MODE_OFF) &&
+                    supportedFlashModes.size() == 1)) {
+                flashAvailable = true;
+            }
+        }
+
+        if (LIE_ABOUT_FLASH && flashAvailable) {
+            // TODO: remove this branch
+            Log.w(TAG, "mapFlash - lying; saying we never support flash");
+            flashAvailable = false;
+        }
+
+        /*
+         * flash.info.available
+         */
+        m.set(FLASH_INFO_AVAILABLE, flashAvailable);
+    }
+
+    private static void mapRequest(CameraMetadataNative m, Parameters p) {
+        /*
+         * request.availableCapabilities
+         */
+        int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
+        m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
+
+        /*
+         * request.maxNumOutputStreams
+         */
+        int[] outputStreams = {
+                /* RAW */
+                REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW,
+                /* Processed & Not-Stalling */
+                REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC,
+                /* Processed & Stalling */
+                REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL,
+        };
+        m.set(REQUEST_MAX_NUM_OUTPUT_STREAMS, outputStreams);
+
+        /*
+         * request.maxNumInputStreams
+         */
+        m.set(REQUEST_MAX_NUM_INPUT_STREAMS, REQUEST_MAX_NUM_INPUT_STREAMS_COUNT);
+
+        /*
+         * request.pipelineMaxDepth
+         */
+        m.set(REQUEST_PIPELINE_MAX_DEPTH,
+                (byte)(REQUEST_PIPELINE_MAX_DEPTH_HAL1 + REQUEST_PIPELINE_MAX_DEPTH_OURS));
+    }
+
+    private static void mapSync(CameraMetadataNative m, Parameters p) {
+        /*
+         * sync.maxLatency
+         */
+        m.set(SYNC_MAX_LATENCY, SYNC_MAX_LATENCY_UNKNOWN);
+    }
+
+    private static void appendStreamConfig(
+            ArrayList<StreamConfiguration> configs, int format, List<Camera.Size> sizes) {
+        for (Camera.Size size : sizes) {
+            StreamConfiguration config =
+                    new StreamConfiguration(format, size.width, size.height, /*input*/false);
+            configs.add(config);
+        }
+    }
+
+    /**
+     * Returns -1 if the anti-banding mode string is null, or not supported.
+     */
+    private static int convertAntiBandingMode(final String mode) {
+        if (mode == null) {
+            return -1;
+        }
+        switch(mode) {
+            case Camera.Parameters.ANTIBANDING_OFF: {
+                return CONTROL_AE_ANTIBANDING_MODE_OFF;
+            }
+            case Camera.Parameters.ANTIBANDING_50HZ: {
+                return CONTROL_AE_ANTIBANDING_MODE_50HZ;
+            }
+            case Camera.Parameters.ANTIBANDING_60HZ: {
+                return CONTROL_AE_ANTIBANDING_MODE_60HZ;
+            }
+            case Camera.Parameters.ANTIBANDING_AUTO: {
+                return CONTROL_AE_ANTIBANDING_MODE_AUTO;
+            }
+            default: {
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Returns null if the anti-banding mode enum is not supported.
+     */
+    private static String convertAntiBandingModeToLegacy(int mode) {
+        switch(mode) {
+            case CONTROL_AE_ANTIBANDING_MODE_OFF: {
+                return Camera.Parameters.ANTIBANDING_OFF;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
+                return Camera.Parameters.ANTIBANDING_50HZ;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
+                return Camera.Parameters.ANTIBANDING_60HZ;
+            }
+            case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
+                return Camera.Parameters.ANTIBANDING_AUTO;
+            }
+            default: {
+                return null;
+            }
+        }
+    }
+
+
+    private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
+        int[] legacyFps = new int[2];
+        legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
+        legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
+        return legacyFps;
+    }
+
+    /**
+     * Return the stall duration for a given output jpeg size in nanoseconds.
+     *
+     * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
+     */
+    private static long calculateJpegStallDuration(Camera.Size size) {
+        long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
+        long area = size.width * (long) size.height;
+        long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME_MS * NS_PER_MS /
+                APPROXIMATE_SENSOR_AREA_PX; // 600ms stall for 8mp
+        return baseDuration + area * stallPerArea;
+    }
+
+    /**
+     * Generate capture result metadata from legacy camera parameters.
+     *
+     * @param params a {@link Camera.Parameters} object to generate metadata from.
+     * @param request the {@link CaptureRequest} used for this result.
+     * @param timestamp the timestamp to use for this result in nanoseconds.
+     * @return a {@link CameraMetadataNative} object containing result metadata.
+     */
+    public static CameraMetadataNative convertResultMetadata(Camera.Parameters params,
+                                                      CaptureRequest request,
+                                                      long timestamp) {
+        CameraMetadataNative result = new CameraMetadataNative();
+
+        /*
+         * control
+         */
+        // control.afState
+        if (LIE_ABOUT_AF) {
+            // TODO: Implement autofocus state machine
+            result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE));
+        }
+
+        // control.aeState
+        if (LIE_ABOUT_AE) {
+            // Lie to pass CTS temporarily.
+            // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
+            result.set(CaptureResult.CONTROL_AE_STATE,
+                    CONTROL_AE_STATE_CONVERGED);
+
+            result.set(CaptureResult.CONTROL_AE_MODE,
+                    request.get(CaptureRequest.CONTROL_AE_MODE));
+        }
+
+        // control.awbLock
+        result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());
+
+        // control.awbState
+        if (LIE_ABOUT_AWB) {
+            // Lie to pass CTS temporarily.
+            // TODO: CTS needs to be updated not to query this value
+            // for LIMITED devices unless its guaranteed to be available.
+            result.set(CaptureResult.CONTROL_AWB_STATE,
+                    CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
+            // TODO: Read the awb mode from parameters instead
+            result.set(CaptureResult.CONTROL_AWB_MODE,
+                    request.get(CaptureRequest.CONTROL_AWB_MODE));
+        }
+
+        /*
+         * lens
+         */
+        // lens.focalLength
+        result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+
+        /*
+         * sensor
+         */
+        // sensor.timestamp
+        result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
+
+        // TODO: Remaining result metadata tags conversions.
+        return result;
+    }
+
+    /**
+     * Set the legacy parameters using the request metadata.
+     *
+     * @param request a {@link CaptureRequest} object to generate parameters from.
+     * @param params the a {@link Camera.Parameters} to set parameters in.
+     */
+    public static void convertRequestMetadata(CaptureRequest request,
+            /*out*/Camera.Parameters params) {
+
+        /*
+         * control.ae*
+         */
+        // control.aeAntibandingMode
+        Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
+        if (antiBandingMode != null) {
+            String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
+            if (legacyMode != null) params.setAntibanding(legacyMode);
+        }
+
+        // control.aeTargetFpsRange
+        Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+        if (aeFpsRange != null) {
+            int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
+            params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                    legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+        }
+
+        /*
+         * control
+         */
+        // control.awbLock
+        Boolean awbLock = request.get(CaptureRequest.CONTROL_AWB_LOCK);
+        params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
+    }
+
+    /**
+     * Create an int[] from the List<> by using {@code convertFrom} and {@code convertTo}
+     * as a one-to-one map (via the index).
+     *
+     * <p>Strings not appearing in {@code convertFrom} are ignored (with a warning);
+     * strings appearing in {@code convertFrom} but not {@code convertTo} are silently
+     * dropped.</p>
+     *
+     * @param list Source list of strings
+     * @param convertFrom Conversion list of strings
+     * @param convertTo Conversion list of ints
+     * @return An array of ints where the values correspond to the ones in {@code convertTo}
+     *         or {@code null} if {@code list} was {@code null}
+     */
+    private static int[] convertStringListToIntArray(
+            List<String> list, String[] convertFrom, int[] convertTo) {
+        if (list == null) {
+            return null;
+        }
+
+        List<Integer> convertedList = new ArrayList<>(list.size());
+
+        for (String str : list) {
+            int strIndex = getArrayIndex(convertFrom, str);
+
+            // Guard against bad API1 values
+            if (strIndex < 0) {
+                Log.w(TAG, "Ignoring invalid parameter " + str);
+                continue;
+            }
+
+            // Ignore values we can't map into (intentional)
+            if (strIndex < convertTo.length) {
+                convertedList.add(convertTo[strIndex]);
+            }
+        }
+
+        int[] returnArray = new int[convertedList.size()];
+        for (int i = 0; i < returnArray.length; ++i) {
+            returnArray[i] = convertedList.get(i);
+        }
+
+        return returnArray;
+    }
+
+    /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
+    private static <T> int getArrayIndex(T[] array, T needle) {
+        if (needle == null) {
+            return -1;
+        }
+
+        int index = 0;
+        for (T elem : array) {
+            if (Objects.equals(elem, needle)) {
+                return index;
+            }
+            index++;
+        }
+
+        return -1;
+    }
+
+    /**
+     * Create a request template
+     *
+     * @param c a non-{@code null} camera characteristics for this camera
+     * @param templateId a non-negative template ID
+     *
+     * @return a non-{@code null} request template
+     *
+     * @throws IllegalArgumentException if {@code templateId} was invalid
+     *
+     * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
+     */
+    public static CameraMetadataNative createRequestTemplate(
+            CameraCharacteristics c, int templateId) {
+        if (templateId < 0 || templateId > CameraDevice.TEMPLATE_MANUAL) {
+            throw new IllegalArgumentException("templateId out of range");
+        }
+
+        CameraMetadataNative m = new CameraMetadataNative();
+
+        /*
+         * NOTE: If adding new code here and it needs to query the static info,
+         * query the camera characteristics, so we can reuse this for api2 code later
+         * to create our own templates in the framework
+         */
+
+        if (LIE_ABOUT_AWB) {
+            m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
+        } else {
+            throw new AssertionError("Valid control.awbMode not implemented yet");
+        }
+
+        // control.aeMode
+        m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
+        // AE is always unconditionally available in API1 devices
+
+        // control.afMode
+        {
+            Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+
+            int afMode;
+            if (minimumFocusDistance != null &&
+                    minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
+                // Cannot control auto-focus with fixed-focus cameras
+                afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
+            } else {
+                // If a minimum focus distance is reported; the camera must have AF
+                afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
+            }
+
+            m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
+        }
+
+        // TODO: map other request template values
+        return m;
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 8a9052f..e674736 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Log;
 import android.view.Surface;
 
 import java.util.Collection;
@@ -26,6 +27,7 @@
  * Immutable container for a single capture request and associated information.
  */
 public class RequestHolder {
+    private static final String TAG = "RequestHolder";
 
     private final boolean mRepeating;
     private final CaptureRequest mRequest;
@@ -89,8 +91,12 @@
      */
     public boolean hasJpegTargets() {
         for (Surface s : getHolderTargets()) {
-            if (jpegType(s)) {
-                return true;
+            try {
+                if (jpegType(s)) {
+                    return true;
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
             }
         }
         return false;
@@ -102,8 +108,12 @@
      */
     public boolean hasPreviewTargets() {
         for (Surface s : getHolderTargets()) {
-            if (previewType(s)) {
-                return true;
+            try {
+                if (previewType(s)) {
+                    return true;
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
             }
         }
         return false;
@@ -115,8 +125,12 @@
      */
     public Surface getFirstPreviewTarget() {
         for (Surface s : getHolderTargets()) {
-            if (previewType(s)) {
-                return s;
+            try {
+                if (previewType(s)) {
+                    return s;
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
             }
         }
         return null;
@@ -128,8 +142,9 @@
      * @param s a {@link Surface} to check.
      * @return true if the surface requires a jpeg buffer.
      */
-    public static boolean jpegType(Surface s) {
-        if (LegacyCameraDevice.nativeDetectSurfaceType(s) ==
+    public static boolean jpegType(Surface s)
+            throws LegacyExceptionUtils.BufferQueueAbandonedException {
+        if (LegacyCameraDevice.detectSurfaceType(s) ==
                 CameraMetadataNative.NATIVE_JPEG_FORMAT) {
             return true;
         }
@@ -149,8 +164,9 @@
      * @param s a {@link Surface} to check.
      * @return true if the surface requires a non-jpeg buffer type.
      */
-    public static boolean previewType(Surface s) {
-        if (LegacyCameraDevice.nativeDetectSurfaceType(s) !=
+    public static boolean previewType(Surface s)
+            throws LegacyExceptionUtils.BufferQueueAbandonedException {
+        if (LegacyCameraDevice.detectSurfaceType(s) !=
                 CameraMetadataNative.NATIVE_JPEG_FORMAT) {
             return true;
         }
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
index 5c68303..7820648 100644
--- a/core/java/android/hardware/camera2/legacy/RequestQueue.java
+++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java
@@ -77,7 +77,8 @@
         long ret = INVALID_FRAME;
         if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
             mRepeatingRequest = null;
-            ret = mCurrentRepeatingFrameNumber;
+            ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
+                    mCurrentRepeatingFrameNumber - 1;
             mCurrentRepeatingFrameNumber = INVALID_FRAME;
         } else {
             Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
@@ -105,7 +106,8 @@
         long ret = INVALID_FRAME;
         if (burst.isRepeating()) {
             if (mRepeatingRequest != null) {
-                ret = mCurrentRepeatingFrameNumber;
+                ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
+                        mCurrentRepeatingFrameNumber - 1;
             }
             mCurrentRepeatingFrameNumber = INVALID_FRAME;
             mRepeatingRequest = burst;
@@ -122,7 +124,7 @@
         for (BurstHolder b : mRequestQueue) {
             total += b.getNumberOfRequests();
             if (b.getRequestId() == requestId) {
-                return total;
+                return total - 1;
             }
         }
         throw new IllegalStateException(
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index c4669f5..73b84656 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -16,7 +16,6 @@
 
 package android.hardware.camera2.legacy;
 
-import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.camera2.CaptureRequest;
@@ -28,12 +27,15 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Size;
 import android.view.Surface;
 
 import java.io.IOError;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -62,23 +64,31 @@
     private static final int MSG_CLEANUP = 3;
 
     private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
-    private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
+    private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
 
+    private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
     private boolean mPreviewRunning = false;
 
+    private volatile long mLastJpegTimestamp;
+    private volatile long mLastPreviewTimestamp;
     private volatile RequestHolder mInFlightPreview;
     private volatile RequestHolder mInFlightJpeg;
 
-    private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
-    private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
+    private final List<Surface> mPreviewOutputs = new ArrayList<Surface>();
+    private final List<Surface> mCallbackOutputs = new ArrayList<Surface>();
     private GLThreadManager mGLThreadManager;
     private SurfaceTexture mPreviewTexture;
+    private Camera.Parameters mParams;
+
+    private Size mIntermediateBufferSize;
 
     private final RequestQueue mRequestQueue = new RequestQueue();
+    private CaptureRequest mLastRequest = null;
     private SurfaceTexture mDummyTexture;
     private Surface mDummySurface;
 
     private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
+    private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
 
     /**
      * Container object for Configure messages.
@@ -93,6 +103,31 @@
         }
     }
 
+
+    /**
+     * Comparator for {@link Size} objects by the area.
+     *
+     * <p>This comparator totally orders by rectangle area. Tiebreaks on width.</p>
+     */
+    private static class SizeAreaComparator implements Comparator<Size> {
+        @Override
+        public int compare(Size size, Size size2) {
+            if (size == null || size2 == null) {
+                throw new NullPointerException("Null argument passed to compare");
+            }
+            if (size.equals(size2)) return 0;
+            long width = size.getWidth();
+            long width2 = size2.getWidth();
+            long area = width * size.getHeight();
+            long area2 = width2 * size2.getHeight();
+            if (area == area2) {
+                return (width > width2) ? 1 : -1;
+            }
+            return (area > area2) ? 1 : -1;
+
+        }
+    }
+
     /**
      * Counter class used to calculate and log the current FPS of frame production.
      */
@@ -166,34 +201,52 @@
                 return;
             }
             for (Surface s : holder.getHolderTargets()) {
-                if (RequestHolder.jpegType(s)) {
-                    Log.i(TAG, "Producing jpeg buffer...");
-                    LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
-                    LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
-                            CameraMetadataNative.NATIVE_JPEG_FORMAT);
+                try {
+                    if (RequestHolder.jpegType(s)) {
+                        Log.i(TAG, "Producing jpeg buffer...");
+                        LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1);
+                        LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
+                                CameraMetadataNative.NATIVE_JPEG_FORMAT);
+                    }
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
                 }
             }
             mReceivedJpeg.open();
         }
     };
 
+    private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
+        @Override
+        public void onShutter() {
+            mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+        }
+    };
+
     private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
             new SurfaceTexture.OnFrameAvailableListener() {
                 @Override
                 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                    RequestHolder holder = mInFlightPreview;
+                    if (holder == null) {
+                        mGLThreadManager.queueNewFrame(null);
+                        Log.w(TAG, "Dropping preview frame.");
+                        return;
+                    }
+
                     if (DEBUG) {
                         mPrevCounter.countAndLog();
                     }
-                    RequestHolder holder = mInFlightPreview;
-                    if (holder == null) {
-                        Log.w(TAG, "Dropping preview frame.");
-                        mInFlightPreview = null;
-                        return;
-                    }
+                    mInFlightPreview = null;
+
                     if (holder.hasPreviewTargets()) {
                         mGLThreadManager.queueNewFrame(holder.getHolderTargets());
                     }
 
+                    /**
+                     * TODO: Get timestamp from GL thread after buffer update.
+                     */
+                    mLastPreviewTimestamp = surfaceTexture.getTimestamp();
                     mReceivedPreview.open();
                 }
             };
@@ -213,14 +266,18 @@
     }
 
     private void doJpegCapture(RequestHolder request) throws IOException {
+        if (DEBUG) Log.d(TAG, "doJpegCapture");
+
         if (!mPreviewRunning) {
+            if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
+
             createDummySurface();
             mCamera.setPreviewTexture(mDummyTexture);
             startPreview();
         }
         mInFlightJpeg = request;
         // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
-        mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
+        mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
         mPreviewRunning = false;
     }
 
@@ -230,7 +287,13 @@
             return; // Already running
         }
 
-        mPreviewTexture.setDefaultBufferSize(640, 480); // TODO: size selection based on request
+        if (mPreviewTexture == null) {
+            throw new IllegalStateException(
+                    "Preview capture called with no preview surfaces configured.");
+        }
+
+        mPreviewTexture.setDefaultBufferSize(mIntermediateBufferSize.getWidth(),
+                mIntermediateBufferSize.getHeight());
         mCamera.setPreviewTexture(mPreviewTexture);
         Camera.Parameters params = mCamera.getParameters();
         List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
@@ -248,6 +311,7 @@
         startPreview();
     }
 
+
     private void configureOutputs(Collection<Surface> outputs) throws IOException {
         stopPreview();
         if (mGLThreadManager != null) {
@@ -261,17 +325,80 @@
         mInFlightPreview = null;
         mInFlightJpeg = null;
 
-        for (Surface s : outputs) {
-            int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
-            switch (format) {
-                case CameraMetadataNative.NATIVE_JPEG_FORMAT:
-                    mCallbackOutputs.add(s);
-                    break;
-                default:
-                    mPreviewOutputs.add(s);
-                    break;
+        if (outputs != null) {
+            for (Surface s : outputs) {
+                try {
+                    int format = LegacyCameraDevice.detectSurfaceType(s);
+                    switch (format) {
+                        case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+                            mCallbackOutputs.add(s);
+                            break;
+                        default:
+                            mPreviewOutputs.add(s);
+                            break;
+                    }
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.w(TAG, "Surface abandoned, skipping...", e);
+                }
             }
         }
+        mParams = mCamera.getParameters();
+        if (mPreviewOutputs.size() > 0) {
+            List<Size> outputSizes = new ArrayList<>(outputs.size());
+            for (Surface s : mPreviewOutputs) {
+                try {
+                    Size size = LegacyCameraDevice.getSurfaceSize(s);
+                    outputSizes.add(size);
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.w(TAG, "Surface abandoned, skipping...", e);
+                }
+            }
+
+            Size largestOutput = findLargestByArea(outputSizes);
+
+            // Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
+            List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes());
+            Size largestJpegDimen = findLargestByArea(supportedJpegSizes);
+
+            List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes());
+
+            // Use smallest preview dimension with same aspect ratio as sensor that is >= than all
+            // of the configured output dimensions.  If none exists, fall back to using the largest
+            // supported preview size.
+            long largestOutputArea = largestOutput.getHeight() * (long) largestOutput.getWidth();
+            Size bestPreviewDimen = findLargestByArea(supportedPreviewSizes);
+            for (Size s : supportedPreviewSizes) {
+                long currArea = s.getWidth() * s.getHeight();
+                long bestArea = bestPreviewDimen.getWidth() * bestPreviewDimen.getHeight();
+                if (checkAspectRatiosMatch(largestJpegDimen, s) && (currArea < bestArea &&
+                        currArea >= largestOutputArea)) {
+                    bestPreviewDimen = s;
+                }
+            }
+
+            mIntermediateBufferSize = bestPreviewDimen;
+            if (DEBUG) {
+                Log.d(TAG, "Intermediate buffer selected with dimens: " +
+                        bestPreviewDimen.toString());
+            }
+        } else {
+            mIntermediateBufferSize = null;
+            if (DEBUG) {
+                Log.d(TAG, "No Intermediate buffer selected, no preview outputs were configured");
+            }
+        }
+
+        Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs, mParams);
+        if (smallestSupportedJpegSize != null) {
+            /*
+             * Set takePicture size to the smallest supported JPEG size large enough
+             * to scale/crop out of for the bounding rectangle of the configured JPEG sizes.
+             */
+
+            Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize);
+            mParams.setPictureSize(
+                    smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight());
+        }
 
         // TODO: Detect and optimize single-output paths here to skip stream teeing.
         if (mGLThreadManager == null) {
@@ -282,7 +409,127 @@
         mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
         mGLThreadManager.allowNewFrames();
         mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
-        mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
+        if (mPreviewTexture != null) {
+            mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
+        }
+
+        // TODO: configure the JPEG surface with some arbitrary size
+        // using LegacyCameraDevice.nativeConfigureSurface
+    }
+
+    /**
+     * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger
+     * than all of the configured {@code JPEG} outputs (by both width and height).
+     *
+     * <p>If multiple supported JPEG sizes are larger, select the smallest of them which
+     * still satisfies the above constraint.</p>
+     *
+     * <p>As a result, the returned size is guaranteed to be usable without needing
+     * to upscale any of the outputs. If only one {@code JPEG} surface is used,
+     * then no scaling/cropping is necessary between the taken picture and
+     * the {@code JPEG} output surface.</p>
+     *
+     * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats
+     * @param params api1 parameters (used for reading only)
+     *
+     * @return a size large enough to fit all of the configured {@code JPEG} outputs, or
+     *          {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
+     *          surfaces.
+     */
+    private Size calculatePictureSize(
+            Collection<Surface> callbackOutputs, Camera.Parameters params) {
+        /*
+         * Find the largest JPEG size (if any), from the configured outputs:
+         * - the api1 picture size should be set to the smallest legal size that's at least as large
+         *   as the largest configured JPEG size
+         */
+        List<Size> configuredJpegSizes = new ArrayList<Size>();
+        for (Surface callbackSurface : callbackOutputs) {
+            try {
+                int format = LegacyCameraDevice.detectSurfaceType(callbackSurface);
+
+                if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+                    continue; // Ignore non-JPEG callback formats
+                }
+
+                Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface);
+                configuredJpegSizes.add(jpegSize);
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
+        }
+        if (!configuredJpegSizes.isEmpty()) {
+            /*
+             * Find the largest configured JPEG width, and height, independently
+             * of the rest.
+             *
+             * The rest of the JPEG streams can be cropped out of this smallest bounding
+             * rectangle.
+             */
+            int maxConfiguredJpegWidth = -1;
+            int maxConfiguredJpegHeight = -1;
+            for (Size jpegSize : configuredJpegSizes) {
+                maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ?
+                        jpegSize.getWidth() : maxConfiguredJpegWidth;
+                maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ?
+                        jpegSize.getHeight() : maxConfiguredJpegHeight;
+            }
+            Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight);
+
+            List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+
+            /*
+             * Find the smallest supported JPEG size that can fit the smallest bounding
+             * rectangle for the configured JPEG sizes.
+             */
+            List<Size> candidateSupportedJpegSizes = new ArrayList<>();
+            for (Size supportedJpegSize : supportedJpegSizes) {
+                if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth &&
+                    supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) {
+                    candidateSupportedJpegSizes.add(supportedJpegSize);
+                }
+            }
+
+            if (candidateSupportedJpegSizes.isEmpty()) {
+                throw new AssertionError(
+                        "Could not find any supported JPEG sizes large enough to fit " +
+                        smallestBoundJpegSize);
+            }
+
+            Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes,
+                    new SizeAreaComparator());
+
+            if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) {
+                Log.w(TAG,
+                        String.format(
+                                "configureOutputs - Will need to crop picture %s into "
+                                + "smallest bound size %s",
+                                smallestSupportedJpegSize, smallestBoundJpegSize));
+            }
+
+            return smallestSupportedJpegSize;
+        }
+
+        return null;
+    }
+
+    private static Size findLargestByArea(List<Size> sizes) {
+        return Collections.max(sizes, new SizeAreaComparator());
+    }
+
+    private static boolean checkAspectRatiosMatch(Size a, Size b) {
+        float aAspect = a.getWidth() / (float) a.getHeight();
+        float bAspect = b.getWidth() / (float) b.getHeight();
+
+        return Math.abs(aAspect - bAspect) < ASPECT_RATIO_TOLERANCE;
+    }
+
+    private static List<Size> convertSizeList(List<Camera.Size> sizeList) {
+        List<Size> sizes = new ArrayList<>(sizeList.size());
+        for (Camera.Size s : sizeList) {
+            sizes.add(new Size(s.width, s.height));
+        }
+        return sizes;
     }
 
     // Calculate the highest FPS range supported
@@ -312,7 +559,6 @@
 
     private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
         private boolean mCleanup = false;
-        private List<RequestHolder> mRepeating = null;
 
         @SuppressWarnings("unchecked")
         @Override
@@ -321,10 +567,14 @@
                 return true;
             }
 
+            if (DEBUG) {
+                Log.d(TAG, "Request thread handling message:" + msg.what);
+            }
             switch (msg.what) {
                 case MSG_CONFIGURE_OUTPUTS:
                     ConfigureHolder config = (ConfigureHolder) msg.obj;
-                    Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
+                    int sizes = config.surfaces != null ? config.surfaces.size() : 0;
+                    Log.i(TAG, "Configure outputs: " + sizes +
                             " surfaces configured.");
                     try {
                         configureOutputs(config.surfaces);
@@ -352,7 +602,15 @@
                     List<RequestHolder> requests =
                             nextBurst.first.produceRequestHolders(nextBurst.second);
                     for (RequestHolder holder : requests) {
+                        CaptureRequest request = holder.getRequest();
+                        if (mLastRequest == null || mLastRequest != request) {
+                            mLastRequest = request;
+                            LegacyMetadataMapper.convertRequestMetadata(mLastRequest,
+                                /*out*/mParams);
+                            mCamera.setParameters(mParams);
+                        }
                         mDeviceState.setCaptureStart(holder);
+                        long timestamp = 0;
                         try {
                             if (holder.hasPreviewTargets()) {
                                 mReceivedPreview.close();
@@ -361,23 +619,31 @@
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for preview callback!");
                                 }
+                                timestamp = mLastPreviewTimestamp;
                             }
                             if (holder.hasJpegTargets()) {
                                 mReceivedJpeg.close();
                                 doJpegCapture(holder);
-                                mReceivedJpeg.block();
                                 if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
                                 }
                                 mInFlightJpeg = null;
+                                timestamp = mLastJpegTimestamp;
                             }
                         } catch (IOException e) {
                             // TODO: err handling
                             throw new IOError(e);
                         }
-                        // TODO: Set fields in result.
-                        mDeviceState.setCaptureResult(holder, new CameraMetadataNative());
+                        if (timestamp == 0) {
+                            timestamp = SystemClock.elapsedRealtimeNanos();
+                        }
+                        CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams,
+                                request, timestamp);
+                        mDeviceState.setCaptureResult(holder, result);
+                    }
+                    if (DEBUG) {
+                        mRequestCounter.countAndLog();
                     }
                     break;
                 case MSG_CLEANUP:
@@ -437,6 +703,12 @@
         Handler handler = mRequestThread.waitAndGetHandler();
         handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
         mRequestThread.quitSafely();
+        try {
+            mRequestThread.join();
+        } catch (InterruptedException e) {
+            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
+                    mRequestThread.getName(), mRequestThread.getId()));
+        }
     }
 
     /**
@@ -473,12 +745,14 @@
 
 
     /**
-     * Configure with the current output Surfaces.
+     * Configure with the current list of output Surfaces.
      *
      * <p>
      * This operation blocks until the configuration is complete.
      * </p>
      *
+     * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
+     *
      * @param outputs a {@link java.util.Collection} of outputs to configure.
      */
     public void configure(Collection<Surface> outputs) {
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 2f0f6bc..e38624a 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -26,6 +26,7 @@
 import android.opengl.GLES20;
 import android.opengl.Matrix;
 import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 
 import java.nio.ByteBuffer;
@@ -338,22 +339,25 @@
         }
 
         int maxLength = 0;
-        int[] dimens = new int[2];
         for (EGLSurfaceHolder holder : surfaces) {
-            LegacyCameraDevice.nativeDetectSurfaceDimens(holder.surface, dimens);
-            int length = dimens[0] * dimens[1];
-            // Find max surface size, ensure PBuffer can hold this many pixels
-            maxLength = (length > maxLength) ? length : maxLength;
-            int[] surfaceAttribs = {
-                    EGL14.EGL_WIDTH, dimens[0],
-                    EGL14.EGL_HEIGHT, dimens[1],
-                    EGL14.EGL_NONE
-            };
-            holder.width = dimens[0];
-            holder.height = dimens[1];
-            holder.eglSurface =
-                    EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
-            checkEglError("eglCreatePbufferSurface");
+            try {
+                Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
+                int length = size.getWidth() * size.getHeight();
+                // Find max surface size, ensure PBuffer can hold this many pixels
+                maxLength = (length > maxLength) ? length : maxLength;
+                int[] surfaceAttribs = {
+                        EGL14.EGL_WIDTH, size.getWidth(),
+                        EGL14.EGL_HEIGHT, size.getHeight(),
+                        EGL14.EGL_NONE
+                };
+                holder.width = size.getWidth();
+                holder.height = size.getHeight();
+                holder.eglSurface =
+                        EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
+                checkEglError("eglCreatePbufferSurface");
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
         }
         mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
                 .order(ByteOrder.nativeOrder());
@@ -431,17 +435,26 @@
     public void configureSurfaces(Collection<Surface> surfaces) {
         releaseEGLContext();
 
+        if (surfaces == null || surfaces.size() == 0) {
+            Log.w(TAG, "No output surfaces configured for GL drawing.");
+            return;
+        }
+
         for (Surface s : surfaces) {
             // If pixel conversions aren't handled by egl, use a pbuffer
-            if (LegacyCameraDevice.needsConversion(s)) {
-                LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.NV21);
-                EGLSurfaceHolder holder = new EGLSurfaceHolder();
-                holder.surface = s;
-                mConversionSurfaces.add(holder);
-            } else {
-                EGLSurfaceHolder holder = new EGLSurfaceHolder();
-                holder.surface = s;
-                mSurfaces.add(holder);
+            try {
+                if (LegacyCameraDevice.needsConversion(s)) {
+                    LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
+                    EGLSurfaceHolder holder = new EGLSurfaceHolder();
+                    holder.surface = s;
+                    mConversionSurfaces.add(holder);
+                } else {
+                    EGLSurfaceHolder holder = new EGLSurfaceHolder();
+                    holder.surface = s;
+                    mSurfaces.add(holder);
+                }
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
             }
         }
 
@@ -481,6 +494,7 @@
         }
         checkGlError("before updateTexImage");
         mSurfaceTexture.updateTexImage();
+        if (targetSurfaces == null) return;
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (targetSurfaces.contains(holder.surface)) {
                 makeCurrent(holder.eglSurface);
@@ -497,10 +511,14 @@
                 GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
                         GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
                 checkGlError("glReadPixels");
-                int format = LegacyCameraDevice.nativeDetectSurfaceType(holder.surface);
-                LegacyCameraDevice.nativeProduceFrame(holder.surface, mPBufferPixels.array(),
-                        holder.width, holder.height, format);
-                swapBuffers(holder.eglSurface);
+                try {
+                    int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
+                    LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
+                            holder.width, holder.height, format);
+                    swapBuffers(holder.eglSurface);
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+                }
             }
         }
     }
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 3036425..fff171b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -63,6 +63,12 @@
 public final class StreamConfigurationMap {
 
     private static final String TAG = "StreamConfigurationMap";
+
+    /**
+     * Indicates that a minimum frame duration is not available for a particular configuration.
+     */
+    public static final long NO_MIN_FRAME_DURATION = 0;
+
     /**
      * Create a new {@link StreamConfigurationMap}.
      *
@@ -359,7 +365,9 @@
      *
      * @param format an image format from {@link ImageFormat} or {@link PixelFormat}
      * @param size an output-compatible size
-     * @return a minimum frame duration {@code >=} 0 in nanoseconds
+     * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+     *          {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+     *          can only occur on limited mode devices).
      *
      * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
      * @throws NullPointerException if {@code size} was {@code null}
@@ -406,7 +414,9 @@
      *          a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
      *          non-empty array returned by {@link #getOutputSizes(Class)}
      * @param size an output-compatible size
-     * @return a minimum frame duration {@code >=} 0 in nanoseconds
+     * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+     *          {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+     *          can only occur on limited mode devices).
      *
      * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
      * @throws NullPointerException if {@code size} or {@code klass} was {@code null}
@@ -892,7 +902,7 @@
     private long getDurationDefault(int duration) {
         switch (duration) {
             case DURATION_MIN_FRAME:
-                throw new AssertionError("Minimum frame durations are required to be listed");
+                return NO_MIN_FRAME_DURATION;
             case DURATION_STALL:
                 return 0L; // OK. A lack of a stall duration implies a 0 stall duration
             default:
diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
index 40cda08..898c746 100644
--- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
+++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
@@ -47,6 +47,8 @@
      * - POLICY_PROHIBITS
      * - RESOURCE_BUSY
      * - NO_SUCH_DEVICE
+     * - NOT_SUPPORTED
+     * - TOO_MANY_USERS
      */
     public static final int EACCES = -13;
     public static final int EBUSY = -16;
diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java
new file mode 100644
index 0000000..af55055
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/CloseableLock.java
@@ -0,0 +1,330 @@
+/*
+ * 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.camera2.utils;
+
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Implement a shared/exclusive lock that can be closed.
+ *
+ * <p>A shared lock can be acquired if any other shared locks are also acquired. An
+ * exclusive lock acquire will block until all shared locks have been released.</p>
+ *
+ * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
+ * while a lock is already held will immediately succeed.</p>
+ *
+ * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
+ * supported; attempting it will throw an {@link IllegalStateException}.</p>
+ *
+ * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
+ * </p>
+ */
+public class CloseableLock implements AutoCloseable {
+
+    private static final boolean VERBOSE = false;
+
+    private final String TAG = "CloseableLock";
+    private final String mName;
+
+    private volatile boolean mClosed = false;
+
+    /** If an exclusive lock is acquired by some thread. */
+    private boolean mExclusive = false;
+    /**
+     * How many shared locks are acquired by any thread:
+     *
+     * <p>Reentrant locking increments this. If an exclusive lock is held,
+     * this value will stay at 0.</p>
+     */
+    private int mSharedLocks = 0;
+
+    private final ReentrantLock mLock = new ReentrantLock();
+    /** This condition automatically releases mLock when waiting; re-acquiring it after notify */
+    private final Condition mCondition = mLock.newCondition();
+
+    /** How many times the current thread is holding the lock */
+    private final ThreadLocal<Integer> mLockCount =
+        new ThreadLocal<Integer>() {
+            @Override protected Integer initialValue() {
+                return 0;
+            }
+        };
+
+    /**
+     * Helper class to release a lock at the end of a try-with-resources statement.
+     */
+    public class ScopedLock implements AutoCloseable {
+        private ScopedLock() {}
+
+        /** Release the lock with {@link CloseableLock#releaseLock}. */
+        @Override
+        public void close() {
+            releaseLock();
+        }
+    }
+
+    /**
+     * Create a new instance; starts out with 0 locks acquired.
+     */
+    public CloseableLock() {
+        mName = "";
+    }
+
+    /**
+     * Create a new instance; starts out with 0 locks acquired.
+     *
+     * @param name set an optional name for logging functionality
+     */
+    public CloseableLock(String name) {
+        mName = name;
+    }
+
+    /**
+     * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock.
+     *
+     * <p>Marking a lock as closed will fail all further acquisition attempts;
+     * it will also immediately unblock all other threads currently trying to acquire a lock.</p>
+     *
+     * <p>This operation is idempotent; calling it more than once has no effect.</p>
+     *
+     * @throws IllegalStateException
+     *          if an attempt is made to {@code close} while this thread has a lock acquired
+     */
+    @Override
+    public void close() {
+        if (mClosed) {
+            log("close - already closed; ignoring");
+            return;
+        }
+
+        ScopedLock scoper = acquireExclusiveLock();
+        // Already closed by another thread?
+        if (scoper == null) {
+            return;
+        } else if (mLockCount.get() != 1) {
+            // Future: may want to add a #releaseAndClose to allow this.
+            throw new IllegalStateException(
+                    "Cannot close while one or more acquired locks are being held by this " +
+                     "thread; release all other locks first");
+        }
+
+        try {
+            mLock.lock();
+
+            mClosed = true;
+            mExclusive = false;
+            mSharedLocks = 0;
+            mLockCount.remove();
+
+            // Notify all threads that are waiting to unblock and return immediately
+            mCondition.signalAll();
+        } finally {
+            mLock.unlock();
+        }
+
+        log("close - completed");
+    }
+
+    /**
+     * Try to acquire the lock non-exclusively, blocking until the operation completes.
+     *
+     * <p>If the lock has already been closed, or being closed before this operation returns,
+     * the call will immediately return {@code false}.</p>
+     *
+     * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed),
+     * this operation will return immediately. If another thread holds an exclusive lock,
+     * this thread will block until the exclusive lock has been released.</p>
+     *
+     * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is
+     * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p>
+     *
+     * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+     *         was already closed.
+     *
+     * @throws IllegalStateException if this thread is already holding an exclusive lock
+     */
+    public ScopedLock acquireLock() {
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, all further acquisitions will fail
+            if (mClosed) {
+                log("acquire lock early aborted (already closed)");
+                return null;
+            }
+
+            ownedLocks = mLockCount.get();
+
+            // This thread is already holding an exclusive lock
+            if (mExclusive && ownedLocks > 0) {
+                throw new IllegalStateException(
+                        "Cannot acquire shared lock while holding exclusive lock");
+            }
+
+            // Is another thread holding the exclusive lock? Block until we can get in.
+            while (mExclusive) {
+                mCondition.awaitUninterruptibly();
+
+                // Did another thread #close while we were waiting? Unblock immediately.
+                if (mClosed) {
+                    log("acquire lock unblocked aborted (already closed)");
+                    return null;
+                }
+            }
+
+            mSharedLocks++;
+
+            ownedLocks = mLockCount.get() + 1;
+            mLockCount.set(ownedLocks);
+        } finally {
+            mLock.unlock();
+        }
+
+        log("acquired lock (local own count = " + ownedLocks + "");
+        return new ScopedLock();
+    }
+
+    /**
+     * Try to acquire the lock exclusively, blocking until all other threads release their locks.
+     *
+     * <p>If the lock has already been closed, or being closed before this operation returns,
+     * the call will immediately return {@code false}.</p>
+     *
+     * <p>If any other threads are holding a lock, this thread will block until all
+     * other locks are released.</p>
+     *
+     * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported,
+     * and must be matched by an equal number of {@link #releaseLock} calls.</p>
+     *
+     * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+     *         was already closed.
+     *
+     * @throws IllegalStateException
+     *          if an attempt is made to acquire an exclusive lock while already holding a lock
+     */
+    public ScopedLock acquireExclusiveLock() {
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, all further acquisitions will fail
+            if (mClosed) {
+                log("acquire exclusive lock early aborted (already closed)");
+                return null;
+            }
+
+            ownedLocks = mLockCount.get();
+
+            // This thread is already holding a shared lock
+            if (!mExclusive && ownedLocks > 0) {
+                throw new IllegalStateException(
+                        "Cannot acquire exclusive lock while holding shared lock");
+            }
+
+            /*
+             * Is another thread holding the lock? Block until we can get in.
+             *
+             * If we are already holding the lock, always let it through since
+             * we are just reentering the exclusive lock.
+             */
+            while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) {
+                mCondition.awaitUninterruptibly();
+
+             // Did another thread #close while we were waiting? Unblock immediately.
+                if (mClosed) {
+                    log("acquire exclusive lock unblocked aborted (already closed)");
+                    return null;
+                }
+            }
+
+            mExclusive = true;
+
+            ownedLocks = mLockCount.get() + 1;
+            mLockCount.set(ownedLocks);
+        } finally {
+            mLock.unlock();
+        }
+
+        log("acquired exclusive lock (local own count = " + ownedLocks + "");
+        return new ScopedLock();
+    }
+
+    /**
+     * Release a single lock that was acquired.
+     *
+     * <p>Any other other that is blocked and trying to acquire a lock will get a chance
+     * to acquire the lock.</p>
+     *
+     * @throws IllegalStateException if no locks were acquired, or if the lock was already closed
+     */
+    public void releaseLock() {
+        if (mLockCount.get() <= 0) {
+            throw new IllegalStateException(
+                    "Cannot release lock that was not acquired by this thread");
+        }
+
+        int ownedLocks;
+
+        try {
+            mLock.lock();
+
+            // Lock is already closed, it couldn't have been acquired in the first place
+            if (mClosed) {
+                throw new IllegalStateException("Do not release after the lock has been closed");
+            }
+
+            if (!mExclusive) {
+                mSharedLocks--;
+            } else {
+                if (mSharedLocks != 0) {
+                    throw new AssertionError("Too many shared locks " + mSharedLocks);
+                }
+            }
+
+            ownedLocks = mLockCount.get() - 1;
+            mLockCount.set(ownedLocks);
+
+            if (ownedLocks == 0 && mExclusive) {
+                // Wake up any threads that might be waiting for the exclusive lock to be released
+                mExclusive = false;
+                mCondition.signalAll();
+            } else if (ownedLocks == 0 && mSharedLocks == 0) {
+                // Wake up any threads that might be trying to get the exclusive lock
+                mCondition.signalAll();
+            }
+        } finally {
+            mLock.unlock();
+        }
+
+        log("released lock (local lock count " + ownedLocks + ")");
+    }
+
+    private void log(String what) {
+        if (VERBOSE) {
+            Log.v(TAG + "[" + mName + "]", what);
+        }
+    }
+
+}
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index 8ad9463..c87b674 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -101,6 +101,9 @@
     /** Logical address used to indicate it is not initialized or invalid. */
     public static final int ADDR_INVALID = -1;
 
+    /** Logical address used to indicate the source comes from internal device. */
+    public static final int ADDR_INTERNAL = 0xFFFF;
+
     // TODO: Complete the list of CEC messages definition.
     public static final int MESSAGE_FEATURE_ABORT = 0x00;
     public static final int MESSAGE_IMAGE_VIEW_ON = 0x04;
@@ -185,6 +188,7 @@
     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
     public static final int RESULT_ALREADY_IN_PROGRESS = 4;
     public static final int RESULT_EXCEPTION = 5;
+    public static final int RESULT_INCORRECT_MODE = 6;
 
     private static final int[] ADDRESS_TO_TYPE = {
         DEVICE_TV,  // ADDR_TV
diff --git a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.aidl b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.aidl
new file mode 100644
index 0000000..1615910
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hdmi;
+
+parcelable HdmiCecDeviceInfo;
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.aidl b/core/java/android/hardware/hdmi/HdmiPortInfo.aidl
new file mode 100644
index 0000000..157b5b3
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hdmi;
+
+parcelable HdmiPortInfo;
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
new file mode 100644
index 0000000..7b25f8a
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -0,0 +1,164 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class to encapsulate HDMI port information. Contains the capability of the ports such as
+ * HDMI-CEC, MHL, ARC(Audio Return Channel), and physical address assigned to each port.
+ *
+ * @hide
+ */
+@SystemApi
+public final class HdmiPortInfo implements Parcelable {
+    /** HDMI port type: Input */
+    public static final int PORT_INPUT = 0;
+
+    /** HDMI port type: Output */
+    public static final int PORT_OUTPUT = 1;
+
+    private final int mId;
+    private final int mType;
+    private final int mAddress;
+    private final boolean mCecSupported;
+    private final boolean mArcSupported;
+    private final boolean mMhlSupported;
+
+    /**
+     * Constructor.
+     *
+     * @param id identifier assigned to each port. 1 for HDMI port 1
+     * @param type HDMI port input/output type
+     * @param address physical address of the port
+     * @param cec {@code true} if HDMI-CEC is supported on the port
+     * @param mhl {@code true} if MHL is supported on the port
+     * @param arc {@code true} if audio return channel is supported on the port
+     */
+    public HdmiPortInfo(int id, int type, int address, boolean cec, boolean mhl, boolean arc) {
+        mId = id;
+        mType = type;
+        mAddress = address;
+        mCecSupported = cec;
+        mArcSupported = arc;
+        mMhlSupported = mhl;
+    }
+
+    /**
+     * Returns the port id.
+     *
+     * @return port id
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the port type.
+     *
+     * @return port type
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the port address.
+     *
+     * @return port address
+     */
+    public int getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * Returns {@code true} if the port supports HDMI-CEC signaling.
+     *
+     * @return {@code true} if the port supports HDMI-CEC signaling.
+     */
+    public boolean isCecSupported() {
+        return mCecSupported;
+    }
+
+    /**
+     * Returns {@code true} if the port supports MHL signaling.
+     *
+     * @return {@code true} if the port supports MHL signaling.
+     */
+    public boolean isMhlSupported() {
+        return mMhlSupported;
+    }
+
+    /**
+     * Returns {@code true} if the port supports audio return channel.
+     *
+     * @return {@code true} if the port supports audio return channel
+     */
+    public boolean isArcSupported() {
+        return mArcSupported;
+    }
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+
+    /**
+     * A helper class to deserialize {@link HdmiPortInfo} for a parcel.
+     */
+    public static final Parcelable.Creator<HdmiPortInfo> CREATOR =
+            new Parcelable.Creator<HdmiPortInfo>() {
+                @Override
+                public HdmiPortInfo createFromParcel(Parcel source) {
+                    int id = source.readInt();
+                    int type = source.readInt();
+                    int address = source.readInt();
+                    boolean cec = (source.readInt() == 1);
+                    boolean arc = (source.readInt() == 1);
+                    boolean mhl = (source.readInt() == 1);
+                    return new HdmiPortInfo(id, type, address, cec, arc, mhl);
+                }
+
+                @Override
+                public HdmiPortInfo[] newArray(int size) {
+                    return new HdmiPortInfo[size];
+                }
+            };
+
+    /**
+     * Serialize this object into a {@link Parcel}.
+     *
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *        May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeInt(mType);
+        dest.writeInt(mAddress);
+        dest.writeInt(mCecSupported ? 1 : 0);
+        dest.writeInt(mArcSupported ? 1 : 0);
+        dest.writeInt(mMhlSupported ? 1 : 0);
+    }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index 6dc4a4f..85af3d1 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -16,6 +16,8 @@
 package android.hardware.hdmi;
 
 import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system
@@ -33,4 +35,46 @@
     HdmiTvClient(IHdmiControlService service) {
         mService = service;
     }
+
+    // Factory method for HdmiTvClient.
+    // Declared package-private. Accessed by HdmiControlManager only.
+    static HdmiTvClient create(IHdmiControlService service) {
+        return new HdmiTvClient(service);
+    }
+
+    /**
+     * Callback interface used to get the result of {@link #deviceSelect}.
+     */
+    public interface SelectCallback {
+        /**
+         * Called when the operation is finished.
+         *
+         * @param result the result value of {@link #deviceSelect}
+         */
+        void onComplete(int result);
+    }
+
+    /**
+     * Select a CEC logical device to be a new active source.
+     *
+     * @param logicalAddress
+     * @param callback
+     */
+    public void deviceSelect(int logicalAddress, SelectCallback callback) {
+        // TODO: Replace SelectCallback with PartialResult.
+        try {
+            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select device: ", e);
+        }
+    }
+
+    private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+        return new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int result) {
+                callback.onComplete(result);
+            }
+        };
+    }
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 8da38e1..73726fa 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -16,9 +16,14 @@
 
 package android.hardware.hdmi;
 
-import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
+import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
+
+import java.util.List;
 
 /**
  * Binder interface that clients running in the application process
@@ -33,4 +38,14 @@
     void queryDisplayStatus(IHdmiControlCallback callback);
     void addHotplugEventListener(IHdmiHotplugEventListener listener);
     void removeHotplugEventListener(IHdmiHotplugEventListener listener);
+    void addDeviceEventListener(IHdmiDeviceEventListener listener);
+    void deviceSelect(int logicalAddress, IHdmiControlCallback callback);
+    void portSelect(int portId, IHdmiControlCallback callback);
+    void sendKeyEvent(int keyCode, boolean isPressed);
+    List<HdmiPortInfo> getPortInfo();
+    boolean canChangeSystemAudioMode();
+    boolean getSystemAudioMode();
+    void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback);
+    void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
+    void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl
new file mode 100644
index 0000000..c4e5989
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl
@@ -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 android.hardware.hdmi;
+
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * the CEC logical device status change event.
+ *
+ * @hide
+ */
+oneway interface IHdmiDeviceEventListener {
+
+    /**
+     * @param deviceInfo {@link HdmiCecDeviceInfo} of the logical device whose
+     *                   status has changed
+     * @param activated true if the device gets activated
+     */
+    void onStatusChanged(in HdmiCecDeviceInfo deviceInfo, in boolean activated);
+}
diff --git a/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl
new file mode 100644
index 0000000..714bbe7
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.hdmi;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * "System Audio" mode change.
+ *
+ * @hide
+ */
+oneway interface IHdmiSystemAudioModeChangeListener {
+
+    /**
+     * @param enabled true if the device gets activated
+     */
+    void onStatusChanged(in boolean enabled);
+}
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 7faf8ef..396efff 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -68,6 +68,7 @@
  */
 public class ConnectivityManager {
     private static final String TAG = "ConnectivityManager";
+    private static final boolean LEGACY_DBG = true; // STOPSHIP
 
     /**
      * A change in network connectivity has occurred. A default connection has either
@@ -823,6 +824,14 @@
 
         NetworkRequest request = null;
         synchronized (sLegacyRequests) {
+            if (LEGACY_DBG) {
+                Log.d(TAG, "Looking for legacyRequest for netCap with hash: " + netCap + " (" +
+                        netCap.hashCode() + ")");
+                Log.d(TAG, "sLegacyRequests has:");
+                for (NetworkCapabilities nc : sLegacyRequests.keySet()) {
+                    Log.d(TAG, "  " + nc + " (" + nc.hashCode() + ")");
+                }
+            }
             LegacyRequest l = sLegacyRequests.get(netCap);
             if (l != null) {
                 Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
@@ -837,7 +846,7 @@
             request = requestNetworkForFeatureLocked(netCap);
         }
         if (request != null) {
-            Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+            Log.d(TAG, "starting startUsingNetworkFeature for request " + request);
             return PhoneConstants.APN_REQUEST_STARTED;
         } else {
             Log.d(TAG, " request Failed");
@@ -898,6 +907,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
@@ -2362,12 +2372,10 @@
      */
     public static boolean setProcessDefaultNetwork(Network network) {
         if (network == null) {
-            NetworkUtils.unbindProcessToNetwork();
+            return NetworkUtils.unbindProcessToNetwork();
         } else {
-            NetworkUtils.bindProcessToNetwork(network.netId);
+            return NetworkUtils.bindProcessToNetwork(network.netId);
         }
-        // TODO fix return value
-        return true;
     }
 
     /**
@@ -2394,11 +2402,9 @@
      */
     public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
         if (network == null) {
-            NetworkUtils.unbindProcessToNetworkForHostResolution();
+            return NetworkUtils.unbindProcessToNetworkForHostResolution();
         } else {
-            NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
+            return NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
         }
-        // TODO hook up the return value.
-        return true;
     }
 }
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index dd9c39f..b7af374 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.LinkAddress;
+import android.net.RouteInfo;
 
 /**
  * Callback class for receiving events from an INetworkManagementService
@@ -98,4 +99,14 @@
      * @param servers The IP addresses of the DNS servers.
      */
     void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers);
+
+    /**
+     * A route has been added or updated.
+     */
+    void routeUpdated(in RouteInfo route);
+
+    /**
+     * A route has been removed.
+     */
+    void routeRemoved(in RouteInfo route);
 }
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index a14d13f..f1fa3eb 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Pair;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -46,9 +47,18 @@
     private final byte[] address;  // network byte order
     private final int prefixLength;
 
+    private void checkAndMaskAddressAndPrefixLength() {
+        if (address.length != 4 && address.length != 16) {
+            throw new IllegalArgumentException(
+                    "IpPrefix has " + address.length + " bytes which is neither 4 nor 16");
+        }
+        NetworkUtils.maskRawAddress(address, prefixLength);
+    }
+
     /**
      * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in
-     * network byte order and a prefix length.
+     * network byte order and a prefix length. Silently truncates the address to the prefix length,
+     * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}.
      *
      * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long.
      * @param prefixLength the prefix length. Must be &gt;= 0 and &lt;= (32 or 128) (IPv4 or IPv6).
@@ -56,24 +66,46 @@
      * @hide
      */
     public IpPrefix(byte[] address, int prefixLength) {
-        if (address.length != 4 && address.length != 16) {
-            throw new IllegalArgumentException(
-                    "IpPrefix has " + address.length + " bytes which is neither 4 nor 16");
-        }
-        if (prefixLength < 0 || prefixLength > (address.length * 8)) {
-            throw new IllegalArgumentException("IpPrefix with " + address.length +
-                    " bytes has invalid prefix length " + prefixLength);
-        }
         this.address = address.clone();
         this.prefixLength = prefixLength;
-        // TODO: Validate that the non-prefix bits are zero
+        checkAndMaskAddressAndPrefixLength();
     }
 
     /**
+     * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently
+     * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently
+     * converted to {@code 192.0.2.0/24}.
+     *
+     * @param address the IP address. Must be non-null.
+     * @param prefixLength the prefix length. Must be &gt;= 0 and &lt;= (32 or 128) (IPv4 or IPv6).
      * @hide
      */
     public IpPrefix(InetAddress address, int prefixLength) {
-        this(address.getAddress(), prefixLength);
+        // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
+        // which is unnecessary because getAddress() already returns a clone.
+        this.address = address.getAddress();
+        this.prefixLength = prefixLength;
+        checkAndMaskAddressAndPrefixLength();
+    }
+
+    /**
+     * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64".
+     * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24}
+     * is silently converted to {@code 192.0.2.0/24}.
+     *
+     * @param prefix the prefix to parse
+     *
+     * @hide
+     */
+    public IpPrefix(String prefix) {
+        // We don't reuse the (InetAddress, int) constructor because "error: call to this must be
+        // first statement in constructor". We could factor out setting the member variables to an
+        // init() method, but if we did, then we'd have to make the members non-final, or "error:
+        // cannot assign a value to final variable address". So we just duplicate the code here.
+        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix);
+        this.address = ipAndMask.first.getAddress();
+        this.prefixLength = ipAndMask.second;
+        checkAndMaskAddressAndPrefixLength();
     }
 
     /**
@@ -129,7 +161,7 @@
     }
 
     /**
-     * Returns the prefix length of this {@code IpAddress}.
+     * Returns the prefix length of this {@code IpPrefix}.
      *
      * @return the prefix length.
      */
@@ -138,6 +170,20 @@
     }
 
     /**
+     * Returns a string representation of this {@code IpPrefix}.
+     *
+     * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::"}.
+     */
+    public String toString() {
+        try {
+            return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength;
+        } catch(UnknownHostException e) {
+            // Cosmic rays?
+            throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e);
+        }
+    }
+
+    /**
      * Implement the Parcelable interface.
      */
     public int describeContents() {
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index 5246078..f9a25f9 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Pair;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -166,23 +167,9 @@
      * @hide
      */
     public LinkAddress(String address, int flags, int scope) {
-        InetAddress inetAddress = null;
-        int prefixLength = -1;
-        try {
-            String [] pieces = address.split("/", 2);
-            prefixLength = Integer.parseInt(pieces[1]);
-            inetAddress = InetAddress.parseNumericAddress(pieces[0]);
-        } catch (NullPointerException e) {            // Null string.
-        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
-        } catch (NumberFormatException e) {           // Non-numeric prefix.
-        } catch (IllegalArgumentException e) {        // Invalid IP address.
-        }
-
-        if (inetAddress == null || prefixLength == -1) {
-            throw new IllegalArgumentException("Bad LinkAddress params " + address);
-        }
-
-        init(inetAddress, prefixLength, flags, scope);
+        // This may throw an IllegalArgumentException; catching it is the caller's responsibility.
+        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+        init(ipAndMask.first, ipAndMask.second, flags, scope);
     }
 
     /**
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 8eefa0f..8be5cf8 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -31,6 +31,7 @@
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Describes the properties of a network link.
@@ -263,13 +264,32 @@
     }
 
     /**
-     * Adds the given {@link InetAddress} to the list of DNS servers.
+     * Adds the given {@link InetAddress} to the list of DNS servers, if not present.
      *
      * @param dnsServer The {@link InetAddress} to add to the list of DNS servers.
+     * @return true if the DNS server was added, false if it was already present.
      * @hide
      */
-    public void addDnsServer(InetAddress dnsServer) {
-        if (dnsServer != null) mDnses.add(dnsServer);
+    public boolean addDnsServer(InetAddress dnsServer) {
+        if (dnsServer != null && !mDnses.contains(dnsServer)) {
+            mDnses.add(dnsServer);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Replaces the DNS servers in this {@code LinkProperties} with
+     * the given {@link Collection} of {@link InetAddress} objects.
+     *
+     * @param addresses The {@link Collection} of DNS servers to set in this object.
+     * @hide
+     */
+    public void setDnsServers(Collection<InetAddress> dnsServers) {
+        mDnses.clear();
+        for (InetAddress dnsServer: dnsServers) {
+            addDnsServer(dnsServer);
+        }
     }
 
     /**
@@ -334,15 +354,17 @@
     }
 
     /**
-     * Adds a {@link RouteInfo} to this {@code LinkProperties}.  If the {@link RouteInfo}
-     * had an interface name set and that differs from the interface set for this
-     * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.  The
-     * proper course is to add either un-named or properly named {@link RouteInfo}.
+     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the
+     * {@link RouteInfo} had an interface name set and that differs from the interface set for this
+     * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.  The proper
+     * course is to add either un-named or properly named {@link RouteInfo}.
      *
      * @param route A {@link RouteInfo} to add to this object.
+     * @return {@code false} if the route was already present, {@code true} if it was added.
+     *
      * @hide
      */
-    public void addRoute(RouteInfo route) {
+    public boolean addRoute(RouteInfo route) {
         if (route != null) {
             String routeIface = route.getInterface();
             if (routeIface != null && !routeIface.equals(mIfaceName)) {
@@ -350,8 +372,28 @@
                    "Route added with non-matching interface: " + routeIface +
                    " vs. " + mIfaceName);
             }
-            mRoutes.add(routeWithInterface(route));
+            route = routeWithInterface(route);
+            if (!mRoutes.contains(route)) {
+                mRoutes.add(route);
+                return true;
+            }
         }
+        return false;
+    }
+
+    /**
+     * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must
+     * specify an interface and the interface must match the interface of this
+     * {@code LinkProperties}, or it will not be removed.
+     *
+     * @return {@code true} if the route was removed, {@code false} if it was not present.
+     *
+     * @hide
+     */
+    public boolean removeRoute(RouteInfo route) {
+        return route != null &&
+                Objects.equals(mIfaceName, route.getInterface()) &&
+                mRoutes.remove(route);
     }
 
     /**
@@ -517,14 +559,14 @@
     }
 
     /**
-     * Returns true if this link has an IPv6 address.
+     * Returns true if this link has a global preferred IPv6 address.
      *
-     * @return {@code true} if there is an IPv6 address, {@code false} otherwise.
+     * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
      * @hide
      */
-    public boolean hasIPv6Address() {
+    public boolean hasGlobalIPv6Address() {
         for (LinkAddress address : mLinkAddresses) {
-          if (address.getAddress() instanceof Inet6Address) {
+          if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
             return true;
           }
         }
@@ -532,6 +574,80 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 default route.
+     *
+     * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4DefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+          if (r.isIPv4Default()) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 default route.
+     *
+     * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6DefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+          if (r.isIPv6Default()) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv4 DNS server.
+     *
+     * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4DnsServer() {
+        for (InetAddress ia : mDnses) {
+          if (ia instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 DNS server.
+     *
+     * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6DnsServer() {
+        for (InetAddress ia : mDnses) {
+          if (ia instanceof Inet6Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
+     * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
+     * because WifiStateMachine accepts static configurations that only specify an address but not
+     * DNS servers or a default route.
+     *
+     * @return {@code true} if the link is provisioned, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isProvisioned() {
+        return (hasIPv4Address() ||
+                (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
+    }
+
+    /**
      * Compares this {@code LinkProperties} interface name against the target
      *
      * @param target LinkProperties to compare.
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 535bbe2..8142670 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -699,15 +699,15 @@
                 break;
             }
 
-            try {
-                if (enable) {
-                    return mPhoneService.enableApnType(apnType);
-                } else {
-                    return mPhoneService.disableApnType(apnType);
-                }
-            } catch (RemoteException e) {
-                if (retry == 0) getPhoneService(true);
-            }
+//            try {
+//                if (enable) {
+//                    return mPhoneService.enableApnType(apnType);
+//                } else {
+//                    return mPhoneService.disableApnType(apnType);
+//                }
+//            } catch (RemoteException e) {
+//                if (retry == 0) getPhoneService(true);
+//            }
         }
 
         loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 318aabe..9a22d78 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -24,6 +24,7 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import javax.net.SocketFactory;
 
@@ -148,7 +149,9 @@
             // Query a property of the underlying socket to ensure the underlying
             // socket exists so a file descriptor is available to bind to a network.
             socket.getReuseAddress();
-            NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId);
+            if (!NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId)) {
+                throw new SocketException("Failed to bind socket to network.");
+            }
             return socket;
         }
     }
@@ -190,13 +193,20 @@
             }
     };
 
+    @Override
     public boolean equals(Object obj) {
         if (obj instanceof Network == false) return false;
         Network other = (Network)obj;
         return this.netId == other.netId;
     }
 
+    @Override
     public int hashCode() {
         return netId * 11;
     }
+
+    @Override
+    public String toString() {
+        return Integer.toString(netId);
+    }
 }
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/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 352512e..1a9a6378 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -20,6 +20,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
 import android.content.Intent;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -38,7 +39,9 @@
  * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
  * <li>Includes a receiver for {@link #ACTION_SCORE_NETWORKS} guarded by the
  *     {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission which scores networks
- *     and (eventually) calls {@link #updateScores} with the results.
+ *     and (eventually) calls {@link #updateScores} with the results. If this receiver specifies an
+ *     android:label attribute, this label will be used when referring to the application throughout
+ *     system settings; otherwise, the application label will be used.
  * </ul>
  *
  * <p>The system keeps track of an active scorer application; at any time, only this application
@@ -105,7 +108,11 @@
      *         scorer.
      */
     public String getActiveScorerPackage() {
-        return NetworkScorerAppManager.getActiveScorer(mContext);
+        NetworkScorerAppData app = NetworkScorerAppManager.getActiveScorer(mContext);
+        if (app == null) {
+            return null;
+        }
+        return app.mPackageName;
     }
 
     /**
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index 3660e7a..7c61710 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -24,7 +24,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -35,6 +34,8 @@
 /**
  * Internal class for managing the primary network scorer application.
  *
+ * TODO: Rename this to something more generic.
+ *
  * @hide
  */
 public final class NetworkScorerAppManager {
@@ -46,8 +47,21 @@
     /** This class cannot be instantiated. */
     private NetworkScorerAppManager() {}
 
+    public static class NetworkScorerAppData {
+        /** Package name of this scorer app. */
+        public final String mPackageName;
+
+        /** Name of this scorer app for display. */
+        public final CharSequence mScorerName;
+
+        public NetworkScorerAppData(String packageName, CharSequence scorerName) {
+            mScorerName = scorerName;
+            mPackageName = packageName;
+        }
+    }
+
     /**
-     * Returns the list of available scorer app package names.
+     * Returns the list of available scorer apps.
      *
      * <p>A network scorer is any application which:
      * <ul>
@@ -58,8 +72,8 @@
      *
      * @return the list of scorers, or the empty list if there are no valid scorers.
      */
-    public static Collection<String> getAllValidScorers(Context context) {
-        List<String> scorers = new ArrayList<>();
+    public static Collection<NetworkScorerAppData> getAllValidScorers(Context context) {
+        List<NetworkScorerAppData> scorers = new ArrayList<>();
 
         PackageManager pm = context.getPackageManager();
         List<ResolveInfo> receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */);
@@ -81,33 +95,32 @@
                 // approved it as a network scorer.
                 continue;
             }
-            scorers.add(receiverInfo.packageName);
+            // NOTE: loadLabel will attempt to load the receiver's label and fall back to the app
+            // label if none is present.
+            scorers.add(new NetworkScorerAppData(
+                    receiverInfo.packageName, receiverInfo.loadLabel(pm)));
         }
 
         return scorers;
     }
 
     /**
-     * Get the application package name to use for scoring networks.
+     * Get the application to use for scoring networks.
      *
-     * @return the scorer package or null if scoring is disabled (including if no scorer was ever
+     * @return the scorer app info or null if scoring is disabled (including if no scorer was ever
      *     selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
      *     it was disabled or uninstalled).
      */
-    public static String getActiveScorer(Context context) {
+    public static NetworkScorerAppData getActiveScorer(Context context) {
         String scorerPackage = Settings.Global.getString(context.getContentResolver(),
-                Global.NETWORK_SCORER_APP);
-        if (isPackageValidScorer(context, scorerPackage)) {
-            return scorerPackage;
-        } else {
-            return null;
-        }
+                Settings.Global.NETWORK_SCORER_APP);
+        return getScorer(context, scorerPackage);
     }
 
     /**
      * Set the specified package as the default scorer application.
      *
-     * <p>The caller must have permission to write to {@link Settings.Global}.
+     * <p>The caller must have permission to write to {@link android.provider.Settings.Global}.
      *
      * @param context the context of the calling application
      * @param packageName the packageName of the new scorer to use. If null, scoring will be
@@ -125,12 +138,12 @@
         Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
 
         if (packageName == null) {
-            Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP,
-                    null);
+            Settings.Global.putString(context.getContentResolver(),
+                    Settings.Global.NETWORK_SCORER_APP, null);
             return true;
         } else {
             // We only make the change if the new package is valid.
-            if (isPackageValidScorer(context, packageName)) {
+            if (getScorer(context, packageName) != null) {
                 Settings.Global.putString(context.getContentResolver(),
                         Settings.Global.NETWORK_SCORER_APP, packageName);
                 return true;
@@ -143,22 +156,30 @@
 
     /** Determine whether the application with the given UID is the enabled scorer. */
     public static boolean isCallerActiveScorer(Context context, int callingUid) {
-        String defaultApp = getActiveScorer(context);
+        NetworkScorerAppData defaultApp = getActiveScorer(context);
         if (defaultApp == null) {
             return false;
         }
         AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         try {
-            appOpsMgr.checkPackage(callingUid, defaultApp);
+            appOpsMgr.checkPackage(callingUid, defaultApp.mPackageName);
             return true;
         } catch (SecurityException e) {
             return false;
         }
     }
 
-    /** Returns true if the given package is a valid scorer. */
-    public static boolean isPackageValidScorer(Context context, String packageName) {
-        Collection<String> applications = getAllValidScorers(context);
-        return packageName != null && applications.contains(packageName);
+    /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */
+    public static NetworkScorerAppData getScorer(Context context, String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            return null;
+        }
+        Collection<NetworkScorerAppData> applications = getAllValidScorers(context);
+        for (NetworkScorerAppData app : applications) {
+            if (packageName.equals(app.mPackageName)) {
+                return app;
+            }
+        }
+        return null;
     }
 }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index b02f88e..c4b17b6 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -24,6 +24,8 @@
 import java.util.Locale;
 
 import android.util.Log;
+import android.util.Pair;
+
 
 /**
  * Native methods for managing network interfaces.
@@ -116,13 +118,13 @@
      * is by design so an application doesn't accidentally use sockets it thinks are still bound to
      * a particular {@code Network}.
      */
-    public native static void bindProcessToNetwork(int netId);
+    public native static boolean bindProcessToNetwork(int netId);
 
     /**
      * Clear any process specific {@code Network} binding.  This reverts a call to
      * {@link #bindProcessToNetwork}.
      */
-    public native static void unbindProcessToNetwork();
+    public native static boolean unbindProcessToNetwork();
 
     /**
      * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
@@ -136,7 +138,7 @@
      *
      * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
      */
-    public native static void bindProcessToNetworkForHostResolution(int netId);
+    public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
     /**
      * Clears any process specific {@link Network} binding for host resolution.  This does
@@ -144,13 +146,13 @@
      *
      * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
      */
-    public native static void unbindProcessToNetworkForHostResolution();
+    public native static boolean unbindProcessToNetworkForHostResolution();
 
     /**
      * Explicitly binds {@code socketfd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
      */
-    public native static void bindSocketToNetwork(int socketfd, int netId);
+    public native static boolean bindSocketToNetwork(int socketfd, int netId);
 
     /**
      * Convert a IPv4 address from an integer to an InetAddress.
@@ -218,24 +220,17 @@
     }
 
     /**
-     * Get InetAddress masked with prefixLength.  Will never return null.
-     * @param IP address which will be masked with specified prefixLength
-     * @param prefixLength the prefixLength used to mask the IP
+     *  Masks a raw IP address byte array with the specified prefix length.
      */
-    public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
-        if (address == null) {
-            throw new RuntimeException("getNetworkPart doesn't accept null address");
-        }
-
-        byte[] array = address.getAddress();
-
+    public static void maskRawAddress(byte[] array, int prefixLength) {
         if (prefixLength < 0 || prefixLength > array.length * 8) {
-            throw new RuntimeException("getNetworkPart - bad prefixLength");
+            throw new RuntimeException("IP address with " + array.length +
+                    " bytes has invalid prefix length " + prefixLength);
         }
 
         int offset = prefixLength / 8;
-        int reminder = prefixLength % 8;
-        byte mask = (byte)(0xFF << (8 - reminder));
+        int remainder = prefixLength % 8;
+        byte mask = (byte)(0xFF << (8 - remainder));
 
         if (offset < array.length) array[offset] = (byte)(array[offset] & mask);
 
@@ -244,6 +239,16 @@
         for (; offset < array.length; offset++) {
             array[offset] = 0;
         }
+    }
+
+    /**
+     * Get InetAddress masked with prefixLength.  Will never return null.
+     * @param address the IP address to mask with
+     * @param prefixLength the prefixLength used to mask the IP
+     */
+    public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+        byte[] array = address.getAddress();
+        maskRawAddress(array, prefixLength);
 
         InetAddress netPart = null;
         try {
@@ -255,6 +260,30 @@
     }
 
     /**
+     * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
+     * @hide
+     */
+    public static Pair<InetAddress, Integer> parseIpAndMask(String ipAndMaskString) {
+        InetAddress address = null;
+        int prefixLength = -1;
+        try {
+            String[] pieces = ipAndMaskString.split("/", 2);
+            prefixLength = Integer.parseInt(pieces[1]);
+            address = InetAddress.parseNumericAddress(pieces[0]);
+        } catch (NullPointerException e) {            // Null string.
+        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
+        } catch (NumberFormatException e) {           // Non-numeric prefix.
+        } catch (IllegalArgumentException e) {        // Invalid IP address.
+        }
+
+        if (address == null || prefixLength == -1) {
+            throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString);
+        }
+
+        return new Pair<InetAddress, Integer>(address, prefixLength);
+    }
+
+    /**
      * Check if IP address type is consistent between two InetAddress.
      * @return true if both are the same type.  False otherwise.
      */
diff --git a/core/java/android/net/PSKKeyManager.java b/core/java/android/net/PSKKeyManager.java
new file mode 100644
index 0000000..92dd141
--- /dev/null
+++ b/core/java/android/net/PSKKeyManager.java
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import java.net.Socket;
+import javax.crypto.SecretKey;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Provider of key material for pre-shared key (PSK) key exchange used in TLS-PSK cipher suites.
+ *
+ * <h3>Overview of TLS-PSK</h3>
+ *
+ * <p>TLS-PSK is a set of TLS/SSL cipher suites which rely on a symmetric pre-shared key (PSK) to
+ * secure the TLS/SSL connection and mutually authenticate its peers. These cipher suites may be
+ * a more natural fit compared to conventional public key based cipher suites in some scenarios
+ * where communication between peers is bootstrapped via a separate step (for example, a pairing
+ * step) and requires both peers to authenticate each other. In such scenarios a symmetric key (PSK)
+ * can be exchanged during the bootstrapping step, removing the need to generate and exchange public
+ * key pairs and X.509 certificates.</p>
+ *
+ * <p>When a TLS-PSK cipher suite is used, both peers have to use the same key for the TLS/SSL
+ * handshake to succeed. Thus, both peers are implicitly authenticated by a successful handshake.
+ * This removes the need to use a {@code TrustManager} in conjunction with this {@code KeyManager}.
+ * </p>
+ *
+ * <h3>Supporting multiple keys</h3>
+ *
+ * <p>A peer may have multiple keys to choose from. To help choose the right key, during the handshake
+ * the server can provide a <em>PSK identity hint</em> to the client, and the client can provide a
+ * <em>PSK identity</em> to the server. The contents of these two pieces of information are specific
+ * to application-level protocols.</p>
+ *
+ * <p><em>NOTE: Both the PSK identity hint and the PSK identity are transmitted in cleartext.
+ * Moreover, these data are received and processed prior to peer having been authenticated. Thus,
+ * they must not contain or leak key material or other sensitive information, and should be
+ * treated (e.g., parsed) with caution, as untrusted data.</em></p>
+ *
+ * <p>The high-level flow leading to peers choosing a key during TLS/SSL handshake is as follows:
+ * <ol>
+ * <li>Server receives a handshake request from client.
+ * <li>Server replies, optionally providing a PSK identity hint to client.</li>
+ * <li>Client chooses the key.</li>
+ * <li>Client provides a PSK identity of the chosen key to server.</li>
+ * <li>Server chooses the key.</li>
+ * </ol></p>
+ *
+ * <p>In the flow above, either peer can signal that they do not have a suitable key, in which case
+ * the the handshake will be aborted immediately. This may enable a network attacker who does not
+ * know the key to learn which PSK identity hints or PSK identities are supported. If this is a
+ * concern then a randomly generated key should be used in the scenario where no key is available.
+ * This will lead to the handshake aborting later, due to key mismatch -- same as in the scenario
+ * where a key is available -- making it appear to the attacker that all PSK identity hints and PSK
+ * identities are supported.</p>
+ *
+ * <h3>Maximum sizes</h3>
+ *
+ * <p>The maximum supported sizes are as follows:
+ * <ul>
+ * <li>256 bytes for keys (see {@link #MAX_KEY_LENGTH_BYTES}),</li>
+ * <li>128 bytes for PSK identity and PSK identity hint (in modified UTF-8 representation) (see
+ * {@link #MAX_IDENTITY_LENGTH_BYTES} and {@link #MAX_IDENTITY_HINT_LENGTH_BYTES}).</li>
+ * </ul></p>
+ *
+ * <h3>Example</h3>
+ * The following example illustrates how to create an {@code SSLContext} which enables the use of
+ * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained
+ * from it.
+ * <pre> {@code
+ *  PSKKeyManager myPskKeyManager = ...;
+ *
+ * SSLContext sslContext = SSLContext.getInstance("TLS");
+ * sslContext.init(
+ *         new KeyManager[] &#123;myPskKeyManager&#125;,
+ *         new TrustManager[0], // No TrustManagers needed in TLS-PSK
+ *         null // Use the default source of entropy
+ *         );
+ *
+ * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...);
+ * // Enable a TLS-PSK cipher suite (no TLS-PSK cipher suites are enabled by default)
+ * sslSocket.setEnabledCipherSuites(new String[] &#123;"TLS_PSK_WITH_AES_128_CBC_SHA"&#125;);
+ * sslSocket.startHandshake();
+ * }</pre>
+ */
+public interface PSKKeyManager extends com.android.org.conscrypt.PSKKeyManager {
+    // IMPLEMENTATION DETAILS: This class exists only because the default implemenetation of the
+    // TLS/SSL JSSE provider (currently Conscrypt) cannot depend on Android framework classes.
+    // As a result, this framework class simply extends the PSKKeyManager interface from Conscrypt
+    // without adding any new methods or fields. Moreover, for technical reasons (Conscrypt classes
+    // are "hidden") this class replaces the Javadoc of Conscrypt's PSKKeyManager.
+
+    /**
+     * Maximum supported length (in bytes) for PSK identity hint (in modified UTF-8 representation).
+     */
+    int MAX_IDENTITY_HINT_LENGTH_BYTES =
+            com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_HINT_LENGTH_BYTES;
+
+    /** Maximum supported length (in bytes) for PSK identity (in modified UTF-8 representation). */
+    int MAX_IDENTITY_LENGTH_BYTES =
+            com.android.org.conscrypt.PSKKeyManager.MAX_IDENTITY_LENGTH_BYTES;
+
+    /** Maximum supported length (in bytes) for PSK. */
+    int MAX_KEY_LENGTH_BYTES = com.android.org.conscrypt.PSKKeyManager.MAX_KEY_LENGTH_BYTES;
+
+    /**
+     * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided
+     * socket.
+     *
+     * @return PSK identity hint to be provided to the client or {@code null} to provide no hint.
+     */
+    @Override
+    String chooseServerKeyIdentityHint(Socket socket);
+
+    /**
+     * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided
+     * engine.
+     *
+     * @return PSK identity hint to be provided to the client or {@code null} to provide no hint.
+     */
+    @Override
+    String chooseServerKeyIdentityHint(SSLEngine engine);
+
+    /**
+     * Gets the PSK identity to report to the server to help agree on the PSK for the provided
+     * socket.
+     *
+     * @param identityHint identity hint provided by the server or {@code null} if none provided.
+     *
+     * @return PSK identity to provide to the server. {@code null} is permitted but will be
+     *         converted into an empty string.
+     */
+    @Override
+    String chooseClientKeyIdentity(String identityHint, Socket socket);
+
+    /**
+     * Gets the PSK identity to report to the server to help agree on the PSK for the provided
+     * engine.
+     *
+     * @param identityHint identity hint provided by the server or {@code null} if none provided.
+     *
+     * @return PSK identity to provide to the server. {@code null} is permitted but will be
+     *         converted into an empty string.
+     */
+    @Override
+    String chooseClientKeyIdentity(String identityHint, SSLEngine engine);
+
+    /**
+     * Gets the PSK to use for the provided socket.
+     *
+     * @param identityHint identity hint provided by the server to help select the key or
+     *        {@code null} if none provided.
+     * @param identity identity provided by the client to help select the key.
+     *
+     * @return key or {@code null} to signal to peer that no suitable key is available and to abort
+     *         the handshake.
+     */
+    @Override
+    SecretKey getKey(String identityHint, String identity, Socket socket);
+
+    /**
+     * Gets the PSK to use for the provided engine.
+     *
+     * @param identityHint identity hint provided by the server to help select the key or
+     *        {@code null} if none provided.
+     * @param identity identity provided by the client to help select the key.
+     *
+     * @return key or {@code null} to signal to peer that no suitable key is available and to abort
+     *         the handshake.
+     */
+    @Override
+    SecretKey getKey(String identityHint, String identity, SSLEngine engine);
+}
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..129248c 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -63,7 +63,6 @@
      */
     private final String mInterface;
 
-    private final boolean mIsDefault;
     private final boolean mIsHost;
     private final boolean mHasGateway;
 
@@ -128,7 +127,6 @@
         }
         mGateway = gateway;
         mInterface = iface;
-        mIsDefault = isDefault();
         mIsHost = isHost();
     }
 
@@ -215,18 +213,6 @@
                 mDestination.getPrefixLength() == 128);
     }
 
-    private boolean isDefault() {
-        boolean val = false;
-        if (mGateway != null) {
-            if (mGateway instanceof Inet4Address) {
-                val = (mDestination == null || mDestination.getPrefixLength() == 0);
-            } else {
-                val = (mDestination == null || mDestination.getPrefixLength() == 0);
-            }
-        }
-        return val;
-    }
-
     /**
      * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}.
      *
@@ -248,7 +234,7 @@
      * Retrieves the gateway or next hop {@link InetAddress} for this route.
      *
      * @return {@link InetAddress} specifying the gateway or next hop.  This may be
-     &                             {@code null} for a directly-connected route."
+     *                             {@code null} for a directly-connected route."
      */
     public InetAddress getGateway() {
         return mGateway;
@@ -269,7 +255,23 @@
      * @return {@code true} if the destination has a prefix length of 0.
      */
     public boolean isDefaultRoute() {
-        return mIsDefault;
+        return mDestination.getPrefixLength() == 0;
+    }
+
+    /**
+     * Indicates if this route is an IPv4 default route.
+     * @hide
+     */
+    public boolean isIPv4Default() {
+        return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address;
+    }
+
+    /**
+     * Indicates if this route is an IPv6 default route.
+     * @hide
+     */
+    public boolean isIPv6Default() {
+        return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address;
     }
 
     /**
@@ -370,10 +372,9 @@
      *  Returns a hashcode for this <code>RouteInfo</code> object.
      */
     public int hashCode() {
-        return (mDestination == null ? 0 : mDestination.hashCode() * 41)
+        return (mDestination.hashCode() * 41)
                 + (mGateway == null ? 0 :mGateway.hashCode() * 47)
-                + (mInterface == null ? 0 :mInterface.hashCode() * 67)
-                + (mIsDefault ? 3 : 7);
+                + (mInterface == null ? 0 :mInterface.hashCode() * 67);
     }
 
     /**
@@ -387,13 +388,8 @@
      * Implement the Parcelable interface
      */
     public void writeToParcel(Parcel dest, int flags) {
-        if (mDestination == null) {
-            dest.writeByte((byte) 0);
-        } else {
-            dest.writeByte((byte) 1);
-            dest.writeByteArray(mDestination.getAddress().getAddress());
-            dest.writeInt(mDestination.getPrefixLength());
-        }
+        dest.writeByteArray(mDestination.getAddress().getAddress());
+        dest.writeInt(mDestination.getPrefixLength());
 
         if (mGateway == null) {
             dest.writeByte((byte) 0);
@@ -415,17 +411,15 @@
             int prefix = 0;
             InetAddress gateway = null;
 
-            if (in.readByte() == 1) {
-                byte[] addr = in.createByteArray();
-                prefix = in.readInt();
+            byte[] addr = in.createByteArray();
+            prefix = in.readInt();
 
-                try {
-                    destAddr = InetAddress.getByAddress(addr);
-                } catch (UnknownHostException e) {}
-            }
+            try {
+                destAddr = InetAddress.getByAddress(addr);
+            } catch (UnknownHostException e) {}
 
             if (in.readByte() == 1) {
-                byte[] addr = in.createByteArray();
+                addr = in.createByteArray();
 
                 try {
                     gateway = InetAddress.getByAddress(addr);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9218c11..53e87a6 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@
 import android.nfc.INfcAdapterExtras;
 import android.nfc.INfcTag;
 import android.nfc.INfcCardEmulation;
+import android.nfc.INfcLockscreenDispatch;
 import android.os.Bundle;
 
 /**
@@ -52,4 +53,6 @@
 
     void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
     void setP2pModes(int initatorModes, int targetModes);
+
+    void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList);
 }
diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl
new file mode 100644
index 0000000..27e9a8c
--- /dev/null
+++ b/core/java/android/nfc/INfcLockscreenDispatch.aidl
@@ -0,0 +1,12 @@
+package android.nfc;
+
+import android.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface INfcLockscreenDispatch {
+
+    boolean onTagDetected(in Tag tag);
+
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index dd8e41c..be098a8 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -380,6 +380,16 @@
         public Uri[] createBeamUris(NfcEvent event);
     }
 
+
+    /**
+     * A callback to be invoked when an application has registered for receiving
+     * tags at the lockscreen.
+     */
+    public interface NfcLockscreenDispatch {
+        public boolean onTagDetected(Tag tag);
+    }
+
+
     /**
      * Helper to check if this device has FEATURE_NFC, but without using
      * a context.
@@ -1417,6 +1427,26 @@
         }
     }
 
+    public boolean registerLockscreenDispatch(final NfcLockscreenDispatch lockscreenDispatch,
+                                           String[] techList) {
+        try {
+            sService.registerLockscreenDispatch(new INfcLockscreenDispatch.Stub() {
+                @Override
+                public boolean onTagDetected(Tag tag) throws RemoteException {
+                    return lockscreenDispatch.onTagDetected(tag);
+                }
+            }, Tag.techListFromStrings(techList));
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Unable to register LockscreenDispatch", e);
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index 0d261d1..43be702 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -35,6 +35,7 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.HashMap;
 
 /**
  * Represents an NFC tag that has been discovered.
@@ -195,6 +196,41 @@
         return strings;
     }
 
+    static int[] techListFromStrings(String[] techStringList) throws IllegalArgumentException {
+        if (techStringList == null) {
+            throw new IllegalArgumentException("List cannot be null");
+        }
+        int[] techIntList = new int[techStringList.length];
+        HashMap<String, Integer> stringToCodeMap = getTechStringToCodeMap();
+        for (int i = 0; i < techStringList.length; i++) {
+            Integer code = stringToCodeMap.get(techStringList[i]);
+
+            if (code == null) {
+                throw new IllegalArgumentException("Unknown tech type " + techStringList[i]);
+            }
+
+            techIntList[i] = code.intValue();
+        }
+        return techIntList;
+    }
+
+    private static HashMap<String, Integer> getTechStringToCodeMap() {
+        HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>();
+
+        techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP);
+        techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC);
+        techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT);
+        techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF);
+        techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE);
+        techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A);
+        techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B);
+        techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F);
+        techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V);
+        techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE);
+
+        return techStringToCodeMap;
+    }
+
     /**
      * For use by NfcService only.
      * @hide
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
new file mode 100644
index 0000000..f3a95b9
--- /dev/null
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Battery manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class BatteryManagerInternal {
+    /**
+     * Returns true if the device is plugged into any of the specified plug types.
+     */
+    public abstract boolean isPowered(int plugTypeSet);
+
+    /**
+     * Returns the current plug type.
+     */
+    public abstract int getPlugType();
+
+    /**
+     * Returns battery level as a percentage.
+     */
+    public abstract int getBatteryLevel();
+
+    /**
+     * Returns whether we currently consider the battery level to be low.
+     */
+    public abstract boolean getBatteryLevelLow();
+
+    /**
+     * Returns a non-zero value if an unsupported charger is attached.
+     */
+    public abstract int getInvalidCharger();
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e627d49..f7d2bfd 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -171,6 +171,10 @@
     private static final String DATA_CONNECTION_COUNT_DATA = "dcc";
     private static final String WIFI_STATE_TIME_DATA = "wst";
     private static final String WIFI_STATE_COUNT_DATA = "wsc";
+    private static final String WIFI_SUPPL_STATE_TIME_DATA = "wsst";
+    private static final String WIFI_SUPPL_STATE_COUNT_DATA = "wssc";
+    private static final String WIFI_SIGNAL_STRENGTH_TIME_DATA = "wsgt";
+    private static final String WIFI_SIGNAL_STRENGTH_COUNT_DATA = "wsgc";
     private static final String BLUETOOTH_STATE_TIME_DATA = "bst";
     private static final String BLUETOOTH_STATE_COUNT_DATA = "bsc";
     private static final String POWER_USE_SUMMARY_DATA = "pws";
@@ -563,8 +567,8 @@
         public static final int STATE_BRIGHTNESS_SHIFT = 0;
         public static final int STATE_BRIGHTNESS_MASK = 0x7;
         // Constants from SIGNAL_STRENGTH_*
-        public static final int STATE_SIGNAL_STRENGTH_SHIFT = 3;
-        public static final int STATE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_SIGNAL_STRENGTH_SHIFT;
+        public static final int STATE_PHONE_SIGNAL_STRENGTH_SHIFT = 3;
+        public static final int STATE_PHONE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
         // Constants from ServiceState.STATE_*
         public static final int STATE_PHONE_STATE_SHIFT = 6;
         public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT;
@@ -582,7 +586,6 @@
         public static final int STATE_WIFI_SCAN_FLAG = 1<<27;
         public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<26;
         public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25;
-        public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
         // These are on the lower bits used for the command; if they change
         // we need to write another int of data.
         public static final int STATE_SENSOR_ON_FLAG = 1<<23;
@@ -591,17 +594,30 @@
         public static final int STATE_SCREEN_ON_FLAG = 1<<20;
         public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
         public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
-        public static final int STATE_WIFI_ON_FLAG = 1<<17;
         public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
 
         public static final int MOST_INTERESTING_STATES =
             STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
-            | STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG;
+            | STATE_PHONE_IN_CALL_FLAG | STATE_BLUETOOTH_ON_FLAG;
 
         public int states;
 
-        public static final int STATE2_VIDEO_ON_FLAG = 1<<0;
-        public static final int STATE2_LOW_POWER_FLAG = 1<<1;
+        // Constants from WIFI_SUPPL_STATE_*
+        public static final int STATE2_WIFI_SUPPL_STATE_SHIFT = 0;
+        public static final int STATE2_WIFI_SUPPL_STATE_MASK = 0xf;
+        // Values for NUM_WIFI_SIGNAL_STRENGTH_BINS
+        public static final int STATE2_WIFI_SIGNAL_STRENGTH_SHIFT = 4;
+        public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK =
+                0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT;
+
+        public static final int STATE2_LOW_POWER_FLAG = 1<<31;
+        public static final int STATE2_VIDEO_ON_FLAG = 1<<30;
+        public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29;
+        public static final int STATE2_WIFI_ON_FLAG = 1<<28;
+
+        public static final int MOST_INTERESTING_STATES2 =
+            STATE2_LOW_POWER_FLAG | STATE2_WIFI_ON_FLAG;
+
         public int states2;
 
         // The wake lock that was acquired at this point.
@@ -1005,7 +1021,7 @@
     };
 
     public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5;
-    
+
     /**
      * Returns the time in microseconds that the screen has been on with
      * the given brightness
@@ -1153,6 +1169,34 @@
      */
     public abstract int getPhoneDataConnectionCount(int dataType, int which);
 
+    public static final int WIFI_SUPPL_STATE_INVALID = 0;
+    public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
+    public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
+    public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
+    public static final int WIFI_SUPPL_STATE_SCANNING = 4;
+    public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
+    public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
+    public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
+    public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
+    public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
+    public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
+    public static final int WIFI_SUPPL_STATE_DORMANT = 11;
+    public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
+
+    public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED+1;
+
+    static final String[] WIFI_SUPPL_STATE_NAMES = {
+        "invalid", "disconn", "disabled", "inactive", "scanning",
+        "authenticating", "associating", "associated", "4-way-handshake",
+        "group-handshake", "completed", "dormant", "uninit"
+    };
+
+    static final String[] WIFI_SUPPL_STATE_SHORT_NAMES = {
+        "inv", "dsc", "dis", "inact", "scan",
+        "auth", "ascing", "asced", "4-way",
+        "group", "compl", "dorm", "uninit"
+    };
+
     public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
             = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
@@ -1163,13 +1207,11 @@
         new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"),
         new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"),
         new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"),
-        new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running", "Wr"),
         new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"),
         new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
         new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
         new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
         new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
-        new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi", "W"),
         new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
         new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
                 HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
@@ -1178,11 +1220,10 @@
                 HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", "Pst",
                 new String[] {"in", "out", "emergency", "off"},
                 new String[] {"in", "out", "em", "off"}),
-        new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK,
-                HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength", "Pss",
-                SignalStrength.SIGNAL_STRENGTH_NAMES, new String[] {
-                    "0", "1", "2", "3", "4"
-        }),
+        new BitDescription(HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK,
+                HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT, "phone_signal_strength", "Pss",
+                SignalStrength.SIGNAL_STRENGTH_NAMES,
+                new String[] { "0", "1", "2", "3", "4" }),
         new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
                 HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", "Sb",
                 SCREEN_BRIGHTNESS_NAMES, SCREEN_BRIGHTNESS_SHORT_NAMES),
@@ -1190,8 +1231,17 @@
 
     public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS
             = new BitDescription[] {
-        new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
         new BitDescription(HistoryItem.STATE2_LOW_POWER_FLAG, "low_power", "lp"),
+        new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
+        new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Wr"),
+        new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"),
+        new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK,
+                HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss",
+                new String[] { "0", "1", "2", "3", "4" },
+                new String[] { "0", "1", "2", "3", "4" }),
+        new BitDescription(HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK,
+                HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT, "wifi_suppl", "Wsp",
+                WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES),
     };
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
@@ -1250,6 +1300,40 @@
     public abstract int getWifiStateCount(int wifiState, int which);
 
     /**
+     * Returns the time in microseconds that the wifi supplicant has been
+     * in a given state.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiSupplStateTime(int state, long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the wifi supplicant has transitioned
+     * to a given state.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiSupplStateCount(int state, int which);
+
+    public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
+
+    /**
+     * Returns the time in microseconds that WIFI has been running with
+     * the given signal strength.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times WIFI has entered the given signal strength.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
+
+    /**
      * Returns the time in microseconds that bluetooth has been on while the device was
      * running on battery.
      * 
@@ -1780,6 +1864,28 @@
         }
         dumpLine(pw, 0 /* uid */, category, WIFI_STATE_COUNT_DATA, args);
 
+        // Dump wifi suppl state stats
+        args = new Object[NUM_WIFI_SUPPL_STATES];
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            args[i] = getWifiSupplStateTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_TIME_DATA, args);
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            args[i] = getWifiSupplStateCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_COUNT_DATA, args);
+
+        // Dump wifi signal strength stats
+        args = new Object[NUM_WIFI_SIGNAL_STRENGTH_BINS];
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getWifiSignalStrengthTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_TIME_DATA, args);
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getWifiSignalStrengthCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
+
         // Dump bluetooth state stats
         args = new Object[NUM_BLUETOOTH_STATES];
         for (int i=0; i<NUM_BLUETOOTH_STATES; i++) {
@@ -2254,7 +2360,7 @@
                 pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Signal levels:");
+        sb.append("  Phone signal levels:");
         didOne = false;
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
@@ -2372,7 +2478,55 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getPhoneDataConnectionCount(i, which));
+            sb.append(getWifiStateCount(i, which));
+            sb.append("x");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Wifi supplicant states:");
+        didOne = false;
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            final long time = getWifiSupplStateTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n    ");
+            didOne = true;
+            sb.append(WIFI_SUPPL_STATE_NAMES[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+            sb.append(getWifiSupplStateCount(i, which));
+            sb.append("x");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Wifi signal levels:");
+        didOne = false;
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n    ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append("level(");
+            sb.append(i);
+            sb.append(") ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+            sb.append(getWifiSignalStrengthCount(i, which));
             sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
@@ -3039,6 +3193,16 @@
         long lastTime = -1;
         long firstTime = -1;
 
+        void reset() {
+            oldState = oldState2 = 0;
+            oldLevel = -1;
+            oldStatus = -1;
+            oldHealth = -1;
+            oldPlug = -1;
+            oldTemp = -1;
+            oldVolt = -1;
+        }
+
         public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin,
                 boolean verbose) {
             if (!checkin) {
@@ -3062,6 +3226,7 @@
                     pw.print(":");
                 }
                 pw.println("START");
+                reset();
             } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
                     || rec.cmd == HistoryItem.CMD_RESET) {
                 if (checkin) {
@@ -3069,6 +3234,7 @@
                 }
                 if (rec.cmd == HistoryItem.CMD_RESET) {
                     pw.print("RESET:");
+                    reset();
                 }
                 pw.print("TIME:");
                 if (checkin) {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index e42c3fe..3252d19 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -692,10 +692,12 @@
 
     /**
      * Returns the value associated with the given key, or defaultValue if
-     * no mapping of the desired type exists for the given key.
+     * no mapping of the desired type exists for the given key or if a null
+     * value is explicitly associatd with the given key.
      *
      * @param key a String, or null
-     * @param defaultValue Value to return if key does not exist
+     * @param defaultValue Value to return if key does not exist or if a null
+     *     value is associated with the given key.
      * @return the CharSequence value associated with the given key, or defaultValue
      *     if no valid CharSequence object is currently mapped to that key.
      */
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/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 69b828f..08a15eb 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -16,8 +16,6 @@
 
 package android.os;
 
-import android.view.WindowManagerPolicy;
-
 /**
  * Power manager local system service interface.
  *
@@ -57,12 +55,9 @@
 
     public abstract boolean getLowPowerModeEnabled();
 
+    public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
+
     public interface LowPowerModeListener {
         public void onLowPowerModeChanged(boolean enabled);
     }
-
-    public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
-
-    // TODO: Remove this and retrieve as a local service instead.
-    public abstract void setPolicy(WindowManagerPolicy policy);
 }
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..afbf983 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,7 +16,10 @@
 
 package android.os;
 
+import android.util.SparseArray;
+
 import java.io.PrintWriter;
+import java.util.HashMap;
 
 /**
  * Representation of a user on the device.
@@ -66,6 +69,8 @@
 
     final int mHandle;
 
+    private static final SparseArray<UserHandle> userHandles = new SparseArray<UserHandle>();
+
     /**
      * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
      * user.
@@ -124,6 +129,18 @@
         return getUserId(Binder.getCallingUid());
     }
 
+    /** @hide */
+    public static final UserHandle getCallingUserHandle() {
+        int userId = getUserId(Binder.getCallingUid());
+        UserHandle userHandle = userHandles.get(userId);
+        // Intentionally not synchronized to save time
+        if (userHandle == null) {
+            userHandle = new UserHandle(userId);
+            userHandles.put(userId, userHandle);
+        }
+        return userHandle;
+    }
+
     /**
      * Returns the uid that is composed from the userId and the appId.
      * @hide
@@ -145,6 +162,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..96db772 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;
@@ -225,14 +226,14 @@
     public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
 
     /**
-     * Key for user restrictions. Specifies if a user is disallowed from configuring
+     * Key for user restrictions. Specifies if a user is disallowed from controlling
      * applications in Settings. The default value is <code>false</code>.
      * <p>
      * Type: Boolean
      * @see #setUserRestrictions(Bundle)
      * @see #getUserRestrictions()
      */
-    public static final String DISALLOW_CONFIG_APPS = "no_config_apps";
+    public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
 
     /**
      * Key for user restrictions. Specifies if a user is disallowed from mounting
@@ -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.
      *
@@ -692,6 +719,26 @@
 
     /**
      * If the target user is a managed profile of the calling user or the caller
+     * is itself a managed profile, then this returns a copy of the label with
+     * badging for accessibility services like talkback. E.g. passing in "Email"
+     * and it might return "Work Email" for Email in the work profile.
+     *
+     * @param label The label to change.
+     * @param user The target user.
+     * @return A label that combines the original label and a badge as
+     *         determined by the system.
+     */
+    public String getBadgedLabelForUser(String label, UserHandle user) {
+        UserInfo userInfo = getUserIfProfile(user.getIdentifier());
+        if (userInfo != null && userInfo.isManagedProfile()) {
+            return Resources.getSystem().getString(
+                    R.string.managed_profile_label_badge, label);
+        }
+        return label;
+    }
+
+    /**
+     * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a drawable to use as a small
      * icon to include in a view to distinguish it from the original icon.
      *
@@ -826,50 +873,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
@@ -899,7 +902,9 @@
                 ++switchableUserCount;
             }
         }
-        return switchableUserCount > 1;
+        final boolean guestEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.GUEST_USER_ENABLED, 0) == 1;
+        return switchableUserCount > 1 || guestEnabled;
     }
 
     /**
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/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 0418049..23b1e2c 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -1194,7 +1194,14 @@
      * @param args Optional arguments to supply to the fragment.
      */
     public void switchToHeader(String fragmentName, Bundle args) {
-        setSelectedHeader(null);
+        Header selectedHeader = null;
+        for (int i = 0; i < mHeaders.size(); i++) {
+            if (fragmentName.equals(mHeaders.get(i).fragment)) {
+                selectedHeader = mHeaders.get(i);
+                break;
+            }
+        }
+        setSelectedHeader(selectedHeader);
         switchToHeaderInner(fragmentName, args);
     }
 
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 381a5f0..5a0b9e9 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -27,6 +27,7 @@
 import android.view.ViewGroup;
 import android.widget.Adapter;
 import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
 import android.widget.ListView;
 
 /**
@@ -95,6 +96,9 @@
     private int mHighlightedPosition = -1;
     private Drawable mHighlightedDrawable;
 
+    private static ViewGroup.LayoutParams sWrapperLayoutParams = new ViewGroup.LayoutParams(
+            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
     private static class PreferenceLayout implements Comparable<PreferenceLayout> {
         private int resId;
         private int widgetResId;
@@ -235,14 +239,18 @@
 
         // If it's not one of the cached ones, set the convertView to null so that 
         // the layout gets re-created by the Preference.
-        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
+        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 ||
+                (getItemViewType(position) == getHighlightItemViewType())) {
             convertView = null;
         }
         View result = preference.getView(convertView, parent);
         if (position == mHighlightedPosition && mHighlightedDrawable != null) {
-            result.setBackgroundDrawable(mHighlightedDrawable);
+            ViewGroup wrapper = new FrameLayout(parent.getContext());
+            wrapper.setLayoutParams(sWrapperLayoutParams);
+            wrapper.setBackgroundDrawable(mHighlightedDrawable);
+            wrapper.addView(result);
+            result = wrapper;
         }
-        result.setTag(preference.getKey());
         return result;
     }
 
@@ -273,8 +281,16 @@
         return true;
     }
 
+    private int getHighlightItemViewType() {
+        return getViewTypeCount() - 1;
+    }
+
     @Override
     public int getItemViewType(int position) {
+        if (position == mHighlightedPosition) {
+            return getHighlightItemViewType();
+        }
+
         if (!mHasReturnedViewTypeCount) {
             mHasReturnedViewTypeCount = true;
         }
@@ -302,7 +318,7 @@
             mHasReturnedViewTypeCount = true;
         }
         
-        return Math.max(1, mPreferenceLayouts.size());
+        return Math.max(1, mPreferenceLayouts.size()) + 1;
     }
 
 }
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
index e32890d..67f6409 100644
--- a/core/java/android/preference/SeekBarPreference.java
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -40,11 +40,19 @@
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
-        final TypedArray a = context.obtainStyledAttributes(
+        TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes);
         setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax));
         a.recycle();
-        setLayoutResource(com.android.internal.R.layout.preference_widget_seekbar);
+
+        a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
+        final int layoutResId = a.getResourceId(
+                com.android.internal.R.styleable.SeekBarPreference_layout,
+                com.android.internal.R.layout.preference_widget_seekbar);
+        a.recycle();
+
+        setLayoutResource(layoutResId);
     }
 
     public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -52,7 +60,7 @@
     }
 
     public SeekBarPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+        this(context, attrs, com.android.internal.R.attr.seekBarPreferenceStyle);
     }
 
     public SeekBarPreference(Context context) {
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/CallLog.java b/core/java/android/provider/CallLog.java
index 3b0d7ff..8eefd9ce9 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -25,6 +25,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Callable;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.DataUsageFeedback;
+import android.telecomm.Subscription;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CallerInfo;
@@ -276,6 +277,18 @@
         public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
 
         /**
+         * The component name of the subscription in string form.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBSCRIPTION_COMPONENT_NAME = "subscription_component_name";
+
+        /**
+         * The identifier of a subscription that is unique to a specified component.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBSCRIPTION_ID = "subscription_id";
+
+        /**
          * Adds a call to the call log.
          *
          * @param ci the CallerInfo object to get the target contact from.  Can be null
@@ -286,13 +299,14 @@
          *        is set by the network and denotes the number presenting rules for
          *        "allowed", "payphone", "restricted" or "unknown"
          * @param callType enumerated values for "incoming", "outgoing", or "missed"
+         * @param subscription The subscription object describing the provider of the call
          * @param start time stamp for the call in milliseconds
          * @param duration call duration in seconds
          *
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                int presentation, int callType, long start, int duration) {
+                int presentation, int callType, Subscription subscription, long start, int duration) {
             final ContentResolver resolver = context.getContentResolver();
             int numberPresentation = PRESENTATION_ALLOWED;
 
@@ -316,6 +330,14 @@
                 }
             }
 
+            // subscription information
+            String subscriptionComponentString = null;
+            String subscriptionId = null;
+            if (subscription != null) {
+                subscriptionComponentString = subscription.getComponentName().flattenToString();
+                subscriptionId = subscription.getId();
+            }
+
             ContentValues values = new ContentValues(6);
 
             values.put(NUMBER, number);
@@ -323,6 +345,8 @@
             values.put(TYPE, Integer.valueOf(callType));
             values.put(DATE, Long.valueOf(start));
             values.put(DURATION, Long.valueOf(duration));
+            values.put(SUBSCRIPTION_COMPONENT_NAME, subscriptionComponentString);
+            values.put(SUBSCRIPTION_ID, subscriptionId);
             values.put(NEW, Integer.valueOf(1));
             if (callType == MISSED_TYPE) {
                 values.put(IS_READ, Integer.valueOf(0));
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8c7e879..b839613 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -171,8 +171,6 @@
      * A key to a boolean in the "extras" bundle of the cursor.
      * The boolean indicates that the provider did not create a snippet and that the client asking
      * for the snippet should do it (true means the snippeting was deferred to the client).
-     *
-     * @hide
      */
     public static final String DEFERRED_SNIPPETING = "deferred_snippeting";
 
@@ -811,7 +809,6 @@
          * The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED},
          * the contact is not pinned. Also see {@link PinnedPositions}.
          * <P>Type: INTEGER </P>
-         * @hide
          */
         public static final String PINNED = "pinned";
 
@@ -850,7 +847,6 @@
         /**
          * Reference to the row in the RawContacts table holding the contact name.
          * <P>Type: INTEGER REFERENCES raw_contacts(_id)</P>
-         * @hide
          */
         public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
 
@@ -1156,8 +1152,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 +1161,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 +1186,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 +1193,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";
     }
@@ -1599,8 +1606,7 @@
                 CONTENT_URI, "strequent");
 
         /**
-         * The content:// style URI for showing frequently contacted person listing.
-         * @hide
+         * The content:// style URI for showing a list of frequently contacted people.
          */
         public static final Uri CONTENT_FREQUENT_URI = Uri.withAppendedPath(
                 CONTENT_URI, "frequent");
@@ -1683,7 +1689,7 @@
          */
         public static final class Entity implements BaseColumns, ContactsColumns,
                 ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
-                StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+                StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
             /**
              * no public constructor since this is a utility class
              */
@@ -2219,7 +2225,6 @@
          * type.  For applications that need to be aware of the data set, this can
          * be used instead of account type to distinguish sets of data.  This is
          * never intended to be used for specifying accounts.
-         * @hide
          */
         public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
 
@@ -2263,8 +2268,6 @@
          * The default value is "0"
          * </p>
          * <p>Type: INTEGER</p>
-         *
-         * @hide
          */
         public static final String NAME_VERIFIED = "name_verified";
 
@@ -3783,8 +3786,6 @@
          * The package name to use when creating {@link Resources} objects for
          * this data row. This value is only designed for use when building user
          * interfaces, and should not be used to infer the owner.
-         *
-         * @hide
          */
         public static final String RES_PACKAGE = "res_package";
 
@@ -5086,12 +5087,10 @@
     }
 
     /**
-     * Additional column returned by the {@link Contacts#CONTENT_FILTER_URI} providing the
-     * explanation of why the filter matched the contact.  Specifically, it contains the
-     * data elements that matched the query.  The overall number of words in the snippet
-     * can be capped.
-     *
-     * @hide
+     * Additional column returned by
+     * {@link ContactsContract.Contacts#CONTENT_FILTER_URI Contacts.CONTENT_FILTER_URI} explaining
+     * why the filter matched the contact.  Specifically, it contains the data elements that
+     * matched the query.  The overall number of words in the snippet can be capped.
      */
     public static class SearchSnippetColumns {
 
@@ -5101,8 +5100,6 @@
          * <p>
          * The snippet may contain (parts of) several data elements comprising
          * the contact.
-         *
-         * @hide
          */
         public static final String SNIPPET = "snippet";
 
@@ -5118,8 +5115,6 @@
          * tokens can be returned in total. A negative number indicates how many
          * tokens can be returned per occurrence of the search terms.</li>
          * </ul>
-         *
-         * @hide
          */
         public static final String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
 
@@ -5130,8 +5125,6 @@
          * {@link ContactsContract#DEFERRED_SNIPPETING} in the cursor. If it exists, the client
          * should do its own snippeting. If it doesn't exist, the snippet column in the cursor
          * should already contain a snippetized string.
-         *
-         * @hide
          */
         public static final String DEFERRED_SNIPPETING_KEY = "deferred_snippeting";
     }
@@ -6997,8 +6990,6 @@
          * The package name to use when creating {@link Resources} objects for
          * this group. This value is only designed for use when building user
          * interfaces, and should not be used to infer the owner.
-         *
-         * @hide
          */
         public static final String RES_PACKAGE = "res_package";
 
@@ -7006,8 +6997,6 @@
          * The display title of this group to load as a resource from
          * {@link #RES_PACKAGE}, which may be localized.
          * <P>Type: TEXT</P>
-         *
-         * @hide
          */
         public static final String TITLE_RES = "title_res";
 
@@ -7734,8 +7723,8 @@
 
     /**
      * <p>
-     * API allowing applications to send pinning information for specified contacts to the
-     * Contacts Provider.
+     * Contact-specific information about whether or not a contact has been pinned by the user
+     * at a particular position within the system contact application's user interface.
      * </p>
      *
      * <p>
@@ -7760,46 +7749,25 @@
      * pinned positions can be positive integers that range anywhere from 0 to
      * {@link PinnedPositions#UNPINNED}.
      * </p>
-     *
-     * <p>
-     * When using {@link PinnedPositions#UPDATE_URI} to update the pinned positions of
-     * certain contacts, it may make sense for your application to star any pinned contacts
-     * by default. To specify this behavior, set the boolean query parameter
-     * {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned
-     * contacts to be automatically starred and unstarred.
-     * </p>
-     *
-     * @hide
      */
     public static final class PinnedPositions {
-
         /**
          * <p>
-         * This URI allows applications to update pinned positions for a provided set of contacts.
-         * </p>
-         *
-         * <p>
-         * The list of contactIds to pin and their corresponding pinned positions should be
-         * provided in key-value pairs stored in a {@link ContentValues} object where the key
-         * is a valid contactId, while each pinned position is a positive integer.
+         * The method to invoke in order to undemote a formerly demoted contact. The contact id of
+         * the contact must be provided as an argument. If the contact was not previously demoted,
+         * nothing will be done.
          * </p>
          *
          * <p>
          * Example:
          * <pre>
-         * ContentValues values = new ContentValues();
-         * values.put("10", 20);
-         * values.put("12", 2);
-         * values.put("15", PinnedPositions.UNPINNED);
-         * int count = resolver.update(PinnedPositions.UPDATE_URI, values, null, null);
+         * final long contactId = 10;
+         * resolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+         *         String.valueOf(contactId), null);
          * </pre>
-         *
-         * This pins the contact with id 10 at position 20, the contact with id 12 at position 2,
-         * and unpins the contact with id 15.
          * </p>
          */
-        public static final Uri UPDATE_URI = Uri.withAppendedPath(AUTHORITY_URI,
-                "pinned_position_update");
+        public static final String UNDEMOTE_METHOD = "undemote";
 
         /**
          * Default value for the pinned position of an unpinned contact. Also equal to
@@ -7814,65 +7782,16 @@
          * just hidden from view.
          */
         public static final int DEMOTED = -1;
-
-        /**
-         * <p> Clients can provide this value as a pinned position to undemote a formerly demoted
-         * contact. If the contact was formerly demoted, it will be restored to an
-         * {@link #UNPINNED} position. If it was otherwise already pinned at another position,
-         * it will not be affected.
-         * </p>
-         *
-         * <p>
-         * Example:
-         * <pre>
-         * ContentValues values = new ContentValues();
-         * values.put("15", PinnedPositions.UNDEMOTE);
-         * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
-         *          .build(), values, null, null);
-         * </pre>
-         *
-         * This restores the contact with id 15 to an {@link #UNPINNED} position, meaning that
-         * other apps (e.g. the Dialer) that were formerly hiding this contact from view based on
-         * its {@link #DEMOTED} position will start displaying it again.
-         * </p>
-         */
-        public static final String UNDEMOTE = "undemote";
-
-        /**
-         * <p>
-         * A boolean query parameter that can be used with {@link #UPDATE_URI}.
-         * If "1" or "true", any contact that is pinned or unpinned will be correspondingly
-         * starred or unstarred. Otherwise, starring information will not be affected by pinned
-         * updates. This is false by default.
-         * </p>
-         *
-         * <p>
-         * Example:
-         * <pre>
-         * ContentValues values = new ContentValues();
-         * values.put("10", 20);
-         * values.put("15", PinnedPositions.UNPINNED);
-         * int count = resolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon()
-         *          .appendQueryParameter(PinnedPositions.FORCE_STAR_WHEN_PINNING, "true").build(),
-         *          values, null, null);
-         * </pre>
-         *
-         * This will pin the contact with id 10 at position 20 and star it automatically if not
-         * already starred, and unpin the contact with id 15, and unstar it automatically if not
-         * already unstarred.
-         * </p>
-         */
-        public static final String STAR_WHEN_PINNING = "star_when_pinning";
     }
 
     /**
-     * Helper methods to display QuickContact dialogs that allow users to pivot on
+     * Helper methods to display QuickContact dialogs that display all the information belonging to
      * a specific {@link Contacts} entry.
      */
     public static final class QuickContact {
         /**
-         * Action used to trigger person pivot dialog.
-         * @hide
+         * Action used to launch the system contacts application and bring up a QuickContact dialog
+         * for the provided {@link Contacts} entry.
          */
         public static final String ACTION_QUICK_CONTACT =
                 "com.android.contacts.action.QUICK_CONTACT";
@@ -7892,9 +7811,8 @@
         public static final String EXTRA_MODE = "mode";
 
         /**
-         * Extra used to indicate a list of specific MIME-types to exclude and
-         * not display. Stored as a {@link String} array.
-         * @hide
+         * Extra used to indicate a list of specific MIME-types to exclude and not display in the
+         * QuickContacts dialog. Stored as a {@link String} array.
          */
         public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes";
 
@@ -7952,8 +7870,7 @@
                 actualContext = ((ContextWrapper) actualContext).getBaseContext();
             }
             final int intentFlags = (actualContext instanceof Activity)
-                    ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
-                    : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+                    ? 0 : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
 
             // Launch pivot dialog through intent for now
             final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index cfab1b3..325917e 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -40,6 +40,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
 
 /**
  * The Media provider contains meta data for all available media on both internal
@@ -671,6 +672,7 @@
                         if (sThumbBuf == null) {
                             sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
                         }
+                        Arrays.fill(sThumbBuf, (byte)0);
                         if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
                             bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
                             if (bitmap == null) {
@@ -1886,6 +1888,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..01e1f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2460,6 +2460,18 @@
         public static final String POINTER_SPEED = "pointer_speed";
 
         /**
+         * Whether lock-to-app will be triggered by long-press on recents.
+         * @hide
+         */
+        public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+
+        /**
+         * Whether lock-to-app will lock the keyguard when exiting.
+         * @hide
+         */
+        public static final String LOCK_TO_APP_EXIT_LOCKED = "lock_to_app_exit_locked";
+
+        /**
          * I am the lolrus.
          * <p>
          * Nonzero values indicate that the user has a bukkit.
@@ -4470,6 +4482,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.
@@ -4564,10 +4582,12 @@
         public static final String PAYMENT_SERVICE_SEARCH_URI = "payment_service_search_uri";
 
         /**
-         * If enabled, intercepted notifications will be displayed (not suppressed) in zen mode.
-         * @hide
+         * 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.
+         * <p>
+         * Type : int (0 to show hints, 1 to skip showing hints)
          */
-        public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications";
+        public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
 
         /**
          * This are the settings to be backed up.
@@ -6210,6 +6230,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.
          *
@@ -6523,6 +6551,53 @@
         public static boolean putFloat(ContentResolver cr, String name, float value) {
             return putString(cr, name, Float.toString(value));
         }
+
+
+        /**
+          * Subscription to be used for voice call on a multi sim device. The supported values
+          * are 0 = SUB1, 1 = SUB2 and etc.
+          * @hide
+          */
+        public static final String MULTI_SIM_VOICE_CALL_SUBSCRIPTION = "multi_sim_voice_call";
+
+        /**
+          * Used to provide option to user to select subscription during dial.
+          * The supported values are 0 = disable or 1 = enable prompt.
+          * @hide
+          */
+        public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt";
+
+        /**
+          * Subscription to be used for data call on a multi sim device. The supported values
+          * are 0 = SUB1, 1 = SUB2 and etc.
+          * @hide
+          */
+        public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call";
+
+        /**
+          * Subscription to be used for SMS on a multi sim device. The supported values
+          * are 0 = SUB1, 1 = SUB2 and etc.
+          * @hide
+          */
+        public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms";
+
+       /**
+          * Used to provide option to user to select subscription during send SMS.
+          * The value 1 - enable, 0 - disable
+          * @hide
+          */
+        public static final String MULTI_SIM_SMS_PROMPT = "multi_sim_sms_prompt";
+
+
+
+        /** User preferred subscriptions setting.
+          * This holds the details of the user selected subscription from the card and
+          * the activation status. Each settings string have the coma separated values
+          * iccId,appType,appId,activationStatus,3gppIndex,3gpp2Index
+          * @hide
+         */
+        public static final String[] MULTI_SIM_USER_PREFERRED_SUBS = {"user_preferred_sub1",
+                "user_preferred_sub2","user_preferred_sub3"};
     }
 
     /**
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index b02a79d..6bb3458 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -124,6 +124,19 @@
  *     }
  * }
  * </pre>
+ *
+ * <p>When targeting api level 21 and above, you must declare the service in your manifest file
+ * with the {@link android.Manifest.permission#BIND_DREAM_SERVICE} permission. For example:</p>
+ * <pre>
+ * &lt;service
+ *     android:name=".MyDream"
+ *     android:exported="true"
+ *     android:icon="@drawable/my_icon"
+ *     android:label="@string/my_dream_label"
+ *     android:permission="android.permission.BIND_DREAM_SERVICE" >
+ *  ...
+ * &lt;/service>
+ * </pre>
  */
 public class DreamService extends Service implements Window.Callback {
     private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
@@ -161,6 +174,7 @@
     private boolean mFinished;
     private boolean mCanDoze;
     private boolean mDozing;
+    private boolean mWindowless;
     private DozeHardware mDozeHardware;
 
     private boolean mDebug = false;
@@ -520,6 +534,24 @@
     }
 
     /**
+     * Marks this dream as windowless.  Only available to doze dreams.
+     *
+     * @hide
+     */
+    public void setWindowless(boolean windowless) {
+        mWindowless = windowless;
+    }
+
+    /**
+     * Returns whether or not this dream is windowless.  Only available to doze dreams.
+     *
+     * @hide
+     */
+    public boolean isWindowless() {
+        return mWindowless;
+    }
+
+    /**
      * Returns true if this dream is allowed to doze.
      * <p>
      * The value returned by this method is only meaningful when the dream has started.
@@ -715,6 +747,7 @@
             WindowManagerGlobal.getInstance().closeAll(mWindowToken,
                     this.getClass().getName(), "Dream");
             mWindowToken = null;
+            mCanDoze = false;
         }
     }
 
@@ -744,47 +777,50 @@
 
         mWindowToken = windowToken;
         mCanDoze = canDoze;
-
-        mWindow = PolicyManager.makeNewWindow(this);
-        mWindow.setCallback(this);
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
-        mWindow.setFormat(PixelFormat.OPAQUE);
-
-        if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
-                windowToken, WindowManager.LayoutParams.TYPE_DREAM));
-
-        WindowManager.LayoutParams lp = mWindow.getAttributes();
-        lp.type = WindowManager.LayoutParams.TYPE_DREAM;
-        lp.token = windowToken;
-        lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
-        lp.flags |= ( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
-                    | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
-                    | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0)
-                    | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
-                    );
-        mWindow.setAttributes(lp);
-        mWindow.setWindowManager(null, windowToken, "dream", true);
-
-        applySystemUiVisibilityFlags(
-                (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0),
-                View.SYSTEM_UI_FLAG_LOW_PROFILE);
-
-        try {
-            getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
-        } catch (WindowManager.BadTokenException ex) {
-            // This can happen because the dream manager service will remove the token
-            // immediately without necessarily waiting for the dream to start.
-            // We should receive a finish message soon.
-            Slog.i(TAG, "attach() called after window token already removed, dream will "
-                    + "finish soon");
-            mWindow = null;
-            return;
+        if (mWindowless && !mCanDoze) {
+            throw new IllegalStateException("Only doze dreams can be windowless");
         }
+        if (!mWindowless) {
+            mWindow = PolicyManager.makeNewWindow(this);
+            mWindow.setCallback(this);
+            mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+            mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
+            mWindow.setFormat(PixelFormat.OPAQUE);
 
+            if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
+                    windowToken, WindowManager.LayoutParams.TYPE_DREAM));
+
+            WindowManager.LayoutParams lp = mWindow.getAttributes();
+            lp.type = WindowManager.LayoutParams.TYPE_DREAM;
+            lp.token = windowToken;
+            lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
+            lp.flags |= ( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                        | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+                        | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0)
+                        | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
+                        );
+            mWindow.setAttributes(lp);
+            mWindow.setWindowManager(null, windowToken, "dream", true);
+
+            applySystemUiVisibilityFlags(
+                    (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0),
+                    View.SYSTEM_UI_FLAG_LOW_PROFILE);
+
+            try {
+                getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
+            } catch (WindowManager.BadTokenException ex) {
+                // This can happen because the dream manager service will remove the token
+                // immediately without necessarily waiting for the dream to start.
+                // We should receive a finish message soon.
+                Slog.i(TAG, "attach() called after window token already removed, dream will "
+                        + "finish soon");
+                mWindow = null;
+                return;
+            }
+        }
         // We need to defer calling onDreamingStarted until after onWindowAttached,
         // which is posted to the handler by addView, so we post onDreamingStarted
         // to the handler also.  Need to watch out here in case detach occurs before
@@ -792,7 +828,7 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                if (mWindow != null) {
+                if (mWindow != null || mWindowless) {
                     if (mDebug) Slog.v(TAG, "Calling onDreamingStarted()");
                     mStarted = true;
                     onDreamingStarted();
@@ -878,6 +914,7 @@
                 if (isLowProfile()) pw.print(" lowprofile");
                 if (isFullscreen()) pw.print(" fullscreen");
                 if (isScreenBright()) pw.print(" bright");
+                if (isWindowless()) pw.print(" windowless");
                 if (isDozing()) pw.print(" dozing");
                 pw.println();
             }
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index dd2030b..5fd597b 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -22,46 +22,56 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
+import android.util.Slog;
 
 /**
  * A class that coordinates access to the fingerprint hardware.
- * @hide
  */
 
 public class FingerprintManager {
     private static final String TAG = "FingerprintManager";
     private static final boolean DEBUG = true;
-    private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint";
-    private static final String FINGERPRINT_SERVICE_CLASS =
-            "com.android.service.fingerprint.FingerprintService";
     private static final int MSG_ENROLL_RESULT = 100;
-    private static final int MSG_SCANNED = 101;
-    private static final int MSG_ERROR = 102;
-    private static final int MSG_REMOVED = 103;
+    private static final int MSG_ACQUIRED = 101;
+    private static final int MSG_PROCESSED = 102;
+    private static final int MSG_ERROR = 103;
+    private static final int MSG_REMOVED = 104;
 
+    // Errors generated by layers above HAL
     public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10;
-    public static final int FINGERPRINT_ERROR = -1; // One of the error messages below.
 
-    // Progress messages.
-    public static final int FINGERPRINT_SCANNED = 1;
-    public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2;
+    // Message types.  Must agree with HAL (fingerprint.h)
+    public static final int FINGERPRINT_ERROR = -1;
+    public static final int FINGERPRINT_ACQUIRED = 1;
+    public static final int FINGERPRINT_PROCESSED = 2;
+    public static final int FINGERPRINT_TEMPLATE_ENROLLING = 3;
     public static final int FINGERPRINT_TEMPLATE_REMOVED = 4;
 
-    // Error messages. Must agree with fingerprint HAL definitions.
+    // Error messages. Must agree with HAL (fingerprint.h)
     public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1;
-    public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2;
+    public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2;
     public static final int FINGERPRINT_ERROR_TIMEOUT = 3;
     public static final int FINGERPRINT_ERROR_NO_SPACE = 4;
 
+    // FINGERPRINT_ACQUIRED messages.  Must agree with HAL (fingerprint.h)
+    public static final int FINGERPRINT_ACQUIRED_GOOD = 0;
+    public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1;
+    public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2;
+    public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 4;
+    public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 8;
+    public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 16;
+
     private IFingerprintService mService;
     private FingerprintManagerReceiver mClientReceiver;
     private Context mContext;
+    private IBinder mToken = new Binder();
 
     private Handler mHandler = new Handler() {
         public void handleMessage(android.os.Message msg) {
@@ -70,8 +80,11 @@
                     case MSG_ENROLL_RESULT:
                         mClientReceiver.onEnrollResult(msg.arg1, msg.arg2);
                         break;
-                    case MSG_SCANNED:
-                        mClientReceiver.onScanned(msg.arg1, msg.arg2);
+                    case MSG_ACQUIRED:
+                        mClientReceiver.onAcquired(msg.arg1);
+                        break;
+                    case MSG_PROCESSED:
+                        mClientReceiver.onProcessed(msg.arg1);
                         break;
                     case MSG_ERROR:
                         mClientReceiver.onError(msg.arg1);
@@ -83,45 +96,29 @@
         }
     };
 
-    public FingerprintManager(Context context) {
+    /**
+     * @hide
+     */
+    public FingerprintManager(Context context, IFingerprintService service) {
         mContext = context;
-        // Connect to service...
-        Intent intent = new Intent();
-        intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS);
-        if (!context.bindServiceAsUser(intent, mFingerprintConnection,
-                Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF)) {
-            if (DEBUG) Log.v(TAG, "Can't bind to " + FINGERPRINT_SERVICE_CLASS);
+        mService = service;
+        if (mService == null) {
+            Slog.v(TAG, "FingerprintManagerService was null");
         }
     }
 
-    private final ServiceConnection mFingerprintConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) Log.v(TAG, "Connected to FingerprintService");
-            mService = IFingerprintService.Stub.asInterface(service);
-            try {
-                mService.startListening(mServiceReceiver, getCurrentUserId());
-            } catch (RemoteException e) {
-                if (DEBUG) Log.v(TAG, "Failed to set callback", e);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) Log.v(TAG, "Disconnected from FingerprintService");
-            mService = null;
-        }
-    };
-
     private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
 
         public void onEnrollResult(int fingerprintId,  int remaining) {
             mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget();
         }
 
-        public void onScanned(int fingerprintId, int confidence) {
-            mHandler.obtainMessage(MSG_SCANNED, fingerprintId, confidence)
-                    .sendToTarget();;
+        public void onAcquired(int acquireInfo) {
+            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0).sendToTarget();
+        }
+
+        public void onProcessed(int fingerprintId) {
+            mHandler.obtainMessage(MSG_PROCESSED, fingerprintId, 0).sendToTarget();
         }
 
         public void onError(int error) {
@@ -152,12 +149,14 @@
      */
     public void enroll(long timeout) {
         if (mServiceReceiver == null) {
-            throw new IllegalStateException("enroll: Call registerCallback() first");
+            sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0);
+            return;
         }
         if (mService != null) try {
-            mService.enroll(timeout, getCurrentUserId());
+            mService.enroll(mToken, timeout, getCurrentUserId());
         } catch (RemoteException e) {
             Log.v(TAG, "Remote exception while enrolling: ", e);
+            sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
         }
     }
 
@@ -167,10 +166,19 @@
      * @param fingerprintId
      */
     public void remove(int fingerprintId) {
-        if (mService != null) try {
-            mService.remove(fingerprintId, getCurrentUserId());
-        } catch (RemoteException e) {
-            Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e);
+        if (mServiceReceiver == null) {
+            sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0);
+            return;
+        }
+        if (mService != null) {
+            try {
+                mService.remove(mToken, fingerprintId, getCurrentUserId());
+            } catch (RemoteException e) {
+                Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e);
+            }
+        } else {
+            Log.w(TAG, "remove(): Service not connected!");
+            sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
         }
     }
 
@@ -182,10 +190,13 @@
         mClientReceiver = receiver;
         if (mService != null) {
             try {
-                mService.startListening(mServiceReceiver, getCurrentUserId());
+                mService.startListening(mToken, mServiceReceiver, getCurrentUserId());
             } catch (RemoteException e) {
                 Log.v(TAG, "Remote exception in startListening(): ", e);
             }
+        } else {
+            Log.w(TAG, "startListening(): Service not connected!");
+            sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
         }
     }
 
@@ -202,15 +213,38 @@
      * Stops the client from listening to fingerprint events.
      */
     public void stopListening() {
-        mClientReceiver = null;
         if (mService != null) {
             try {
-                mService.stopListening(getCurrentUserId());
+                mService.stopListening(mToken, getCurrentUserId());
+                mClientReceiver = null;
             } catch (RemoteException e) {
                 Log.v(TAG, "Remote exception in stopListening(): ", e);
             }
         } else {
             Log.w(TAG, "stopListening(): Service not connected!");
+            sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
         }
     }
+
+    public void enrollCancel() {
+        if (mServiceReceiver == null) {
+            sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0);
+            return;
+        }
+        if (mService != null) {
+            try {
+                mService.enrollCancel(mToken, getCurrentUserId());
+                mClientReceiver = null;
+            } catch (RemoteException e) {
+                Log.v(TAG, "Remote exception in enrollCancel(): ", e);
+                sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
+            }
+        } else {
+            Log.w(TAG, "enrollCancel(): Service not connected!");
+        }
+    }
+
+    private void sendError(int msg, int arg1, int arg2) {
+        mHandler.obtainMessage(msg, arg1, arg2);
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java
index 5960791..e5193f5 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 {
@@ -31,18 +30,32 @@
     public void onEnrollResult(int fingerprintId,  int remaining) { }
 
     /**
-     * Fingerprint scan detected. Most clients will use this function to detect a fingerprint
+     * Fingerprint touch detected, but not processed yet. Clients will use this message to
+     * determine a good or bad scan before the fingerprint is processed.  This is meant for the
+     * client to provide feedback about the scan or alert the user that recognition is to follow.
      *
-     * @param fingerprintId is the finger the hardware has detected.
-     * @param confidence from 0 (no confidence) to 65535 (high confidence). Fingerprint 0 has
-     * special meaning - the finger wasn't recognized.
+     * @param acquiredInfo one of:
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_GOOD},
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_PARTIAL},
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_INSUFFICIENT},
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_IMAGER_DIRTY},
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_SLOW},
+     * {@link FingerprintManager#FINGERPRINT_ACQUIRED_TOO_FAST}
      */
-    public void onScanned(int fingerprintId, int confidence) { }
+    public void onAcquired(int acquiredInfo) { }
+
+    /**
+     * Fingerprint has been detected and processed.  A non-zero return indicates a valid
+     * fingerprint was detected.
+     *
+     * @param fingerprintId the finger id, or 0 if not recognized.
+     */
+    public void onProcessed(int fingerprintId) { }
 
     /**
      * An error was detected during scan or enrollment.  One of
      * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE},
-     * {@link FingerprintManager#FINGERPRINT_ERROR_BAD_CAPTURE} or
+     * {@link FingerprintManager#FINGERPRINT_ERROR_UNABLE_TO_PROCESS} or
      * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT}
      * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE}
      *
diff --git a/core/java/android/service/fingerprint/FingerprintService.java b/core/java/android/service/fingerprint/FingerprintService.java
deleted file mode 100644
index c7fa7cd..0000000
--- a/core/java/android/service/fingerprint/FingerprintService.java
+++ /dev/null
@@ -1,219 +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 android.service.fingerprint;
-
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Slog;
-
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-/**
- * A service to manage multiple clients that want to access the fingerprint HAL API.
- * The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint -related events.
- *
- * @hide
- */
-public class FingerprintService extends Service {
-    private final String TAG = FingerprintService.class.getSimpleName() +
-            "[" + getClass().getSimpleName() + "]";
-    private static final boolean DEBUG = true;
-    HashMap<IFingerprintServiceReceiver, ClientData> mClients =
-            new HashMap<IFingerprintServiceReceiver, ClientData>();
-
-    private static final int MSG_NOTIFY = 10;
-
-    Handler mHandler = new Handler() {
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_NOTIFY:
-                    handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj);
-                    break;
-
-                default:
-                    Slog.w(TAG, "Unknown message:" + msg.what);
-            }
-        }
-    };
-
-    private static final int STATE_IDLE = 0;
-    private static final int STATE_LISTENING = 1;
-    private static final int STATE_ENROLLING = 2;
-    private static final int STATE_DELETING = 3;
-    private static final long MS_PER_SEC = 1000;
-
-    private static final class ClientData {
-        public IFingerprintServiceReceiver receiver;
-        int state;
-        int userId;
-    }
-
-    @Override
-    public final IBinder onBind(Intent intent) {
-        if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
-        return new FingerprintServiceWrapper();
-    }
-
-    // JNI methods to communicate from FingerprintManagerService to HAL
-    native int nativeEnroll(int timeout);
-    native int nativeRemove(int fingerprintId);
-
-    // JNI methods for communicating from HAL to clients
-    void notify(int msg, int arg1, int arg2) {
-        mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget();
-    }
-
-    void handleNotify(int msg, int arg1, int arg2) {
-        for (int i = 0; i < mClients.size(); i++) {
-            ClientData clientData = mClients.get(i);
-            switch (msg) {
-                case FingerprintManager.FINGERPRINT_ERROR: {
-                    if (clientData.state != STATE_IDLE) {
-                        // FINGERPRINT_ERROR_HW_UNAVAILABLE
-                        // FINGERPRINT_ERROR_BAD_CAPTURE
-                        // FINGERPRINT_ERROR_TIMEOUT
-                        // FINGERPRINT_ERROR_NO_SPACE
-                        final int error = arg1;
-                        clientData.state = STATE_IDLE;
-                        if (clientData.receiver != null) {
-                            try {
-                                clientData.receiver.onError(error);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "can't send message to client. Did it die?", e);
-                            }
-                        }
-                    }
-                }
-                break;
-                case FingerprintManager.FINGERPRINT_SCANNED: {
-                    final int fingerId = arg1;
-                    final int confidence = arg2;
-                    if (clientData.state == STATE_LISTENING && clientData.receiver != null) {
-                        try {
-                            clientData.receiver.onScanned(fingerId, confidence);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "can't send message to client. Did it die?", e);
-                        }
-                    }
-                    break;
-                }
-                case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: {
-                    if (clientData.state == STATE_ENROLLING) {
-                        final int fingerId = arg1;
-                        final int remaining = arg2;
-                        if (remaining == 0) {
-                            FingerprintUtils.addFingerprintIdForUser(fingerId,
-                                    getContentResolver(), clientData.userId);
-                            clientData.state = STATE_IDLE; // Nothing left to do
-                        }
-                        if (clientData.receiver != null) {
-                            try {
-                                clientData.receiver.onEnrollResult(fingerId, remaining);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "can't send message to client. Did it die?", e);
-                            }
-                        }
-                    }
-                    break;
-                }
-                case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: {
-                    int fingerId = arg1;
-                    if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL");
-                    if (clientData.state == STATE_DELETING) {
-                        FingerprintUtils.removeFingerprintIdForUser(fingerId, getContentResolver(),
-                                clientData.userId);
-                        if (clientData.receiver != null) {
-                            try {
-                                clientData.receiver.onRemoved(fingerId);
-                            } catch (RemoteException e) {
-                                Slog.e(TAG, "can't send message to client. Did it die?", e);
-                            }
-                        }
-                    }
-                }
-                break;
-            }
-        }
-    }
-
-    int enroll(IFingerprintServiceReceiver receiver, long timeout, int userId) {
-        ClientData clientData = mClients.get(receiver);
-        if (clientData != null) {
-            if (clientData.userId != userId) throw new IllegalStateException("Bad user");
-            clientData.state = STATE_ENROLLING;
-            return nativeEnroll((int) (timeout / MS_PER_SEC));
-        }
-        return -1;
-    }
-
-    int remove(IFingerprintServiceReceiver receiver, int fingerId, int userId) {
-        ClientData clientData = mClients.get(receiver);
-        if (clientData != null) {
-            if (clientData.userId != userId) throw new IllegalStateException("Bad user");
-            clientData.state = STATE_DELETING;
-            // The fingerprint id will be removed when we get confirmation from the HAL
-            return nativeRemove(fingerId);
-        }
-        return -1;
-    }
-
-    void startListening(IFingerprintServiceReceiver receiver, int userId) {
-        ClientData clientData = new ClientData();
-        clientData.state = STATE_LISTENING;
-        clientData.receiver = receiver;
-        clientData.userId = userId;
-        mClients.put(receiver, clientData);
-    }
-
-    void stopListening(IFingerprintServiceReceiver receiver, int userId) {
-        ClientData clientData = mClients.get(receiver);
-        if (clientData != null) {
-            clientData.state = STATE_IDLE;
-            clientData.userId = -1;
-            clientData.receiver = null;
-        }
-        mClients.remove(receiver);
-    }
-
-    private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
-        IFingerprintServiceReceiver mReceiver;
-        public int enroll(long timeout, int userId) {
-            return mReceiver != null ? FingerprintService.this.enroll(mReceiver, timeout, userId)
-                    : FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER;
-        }
-
-        public int remove(int fingerprintId, int userId) {
-            return FingerprintService.this.remove(mReceiver, fingerprintId, userId);
-        }
-
-        public void startListening(IFingerprintServiceReceiver receiver, int userId) {
-            mReceiver = receiver;
-            FingerprintService.this.startListening(receiver, userId);
-        }
-
-        public void stopListening(int userId) {
-            FingerprintService.this.stopListening(mReceiver, userId);
-        }
-    }
-}
diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java
index 81a2aac..f4b5526 100644
--- a/core/java/android/service/fingerprint/FingerprintUtils.java
+++ b/core/java/android/service/fingerprint/FingerprintUtils.java
@@ -18,10 +18,12 @@
 
 import android.content.ContentResolver;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.Arrays;
 
+public
 class FingerprintUtils {
     private static final boolean DEBUG = true;
     private static final String TAG = "FingerprintUtils";
@@ -30,13 +32,16 @@
         String fingerIdsRaw = Settings.Secure.getStringForUser(res,
                 Settings.Secure.USER_FINGERPRINT_IDS, userId);
 
-        String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", ");
-        int result[] = new int[fingerStringIds.length];
-        for (int i = 0; i < result.length; i++) {
-            try {
-                result[i] = Integer.decode(fingerStringIds[i]);
-            } catch (NumberFormatException e) {
-                if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]);
+        int result[] = {};
+        if (!TextUtils.isEmpty(fingerIdsRaw)) {
+            String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", ");
+            result = new int[fingerStringIds.length];
+            for (int i = 0; i < result.length; i++) {
+                try {
+                    result[i] = Integer.decode(fingerStringIds[i]);
+                } catch (NumberFormatException e) {
+                    if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]);
+                }
             }
         }
         return result;
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl
index e92c20c..43d5e9a 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintService.aidl
@@ -22,17 +22,20 @@
  * Communication channel from client to the fingerprint service.
  * @hide
  */
-interface IFingerprintService {
-    // Returns 0 if successfully started, -1 otherwise
-    int enroll(long timeout, int userId);
+oneway interface IFingerprintService {
+    // Any errors resulting from this call will be returned to the listener
+    void enroll(IBinder token, long timeout, int userId);
+    
+    // Any errors resulting from this call will be returned to the listener
+    void enrollCancel(IBinder token, int userId);
 
-    // Returns 0 if fingerprintId's template can be removed, -1 otherwise
-    int remove(int fingerprintId, int userId);
+    // Any errors resulting from this call will be returned to the listener
+    void remove(IBinder token, int fingerprintId, int userId);
 
     // Start listening for fingerprint events.  This has the side effect of starting
     // the hardware if not already started.
-    oneway void startListening(IFingerprintServiceReceiver receiver, int userId);
+    void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId);
 
     // Stops listening for fingerprints
-    oneway void stopListening(int userId);
+    void stopListening(IBinder token, int userId);
 }
diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl
index 4826b59..af4128f 100644
--- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl
@@ -24,7 +24,8 @@
  */
 oneway interface IFingerprintServiceReceiver {
     void onEnrollResult(int fingerprintId,  int remaining);
-    void onScanned(int fingerprintId, int confidence);
+    void onAcquired(int acquiredInfo);
+    void onProcessed(int fingerprintId);
     void onError(int error);
     void onRemoved(int fingerprintId);
 }
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index ed835e4..8fa45e6 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() + "]";
@@ -84,6 +80,11 @@
 
     private ITrustAgentServiceCallback mCallback;
 
+    private Runnable mPendingGrantTrustTask;
+
+    // Lock used to access mPendingGrantTrustTask and mCallback.
+    private final Object mLock = new Object();
+
     private Handler mHandler = new Handler() {
         public void handleMessage(android.os.Message msg) {
             switch (msg.what) {
@@ -131,12 +132,24 @@
      * @param initiatedByUser indicates that the user has explicitly initiated an action that proves
      *                        the user is about to use the device.
      */
-    public final void grantTrust(CharSequence message, long durationMs, boolean initiatedByUser) {
-        if (mCallback != null) {
-            try {
-                mCallback.grantTrust(message.toString(), durationMs, initiatedByUser);
-            } catch (RemoteException e) {
-                onError("calling enableTrust()");
+    public final void grantTrust(
+            final CharSequence message, final long durationMs, final boolean initiatedByUser) {
+        synchronized (mLock) {
+            if (mCallback != null) {
+                try {
+                    mCallback.grantTrust(message.toString(), durationMs, initiatedByUser);
+                } catch (RemoteException e) {
+                    onError("calling enableTrust()");
+                }
+            } else {
+                // Remember trust has been granted so we can effectively grant it once the service
+                // is bound.
+                mPendingGrantTrustTask = new Runnable() {
+                    @Override
+                    public void run() {
+                        grantTrust(message, durationMs, initiatedByUser);
+                    }
+                };
             }
         }
     }
@@ -145,11 +158,16 @@
      * Call to revoke trust on the device.
      */
     public final void revokeTrust() {
-        if (mCallback != null) {
-            try {
-                mCallback.revokeTrust();
-            } catch (RemoteException e) {
-                onError("calling revokeTrust()");
+        synchronized (mLock) {
+            if (mPendingGrantTrustTask != null) {
+                mPendingGrantTrustTask = null;
+            }
+            if (mCallback != null) {
+                try {
+                    mCallback.revokeTrust();
+                } catch (RemoteException e) {
+                    onError("calling revokeTrust()");
+                }
             }
         }
     }
@@ -168,7 +186,13 @@
         }
 
         public void setCallback(ITrustAgentServiceCallback callback) {
-            mCallback = callback;
+            synchronized (mLock) {
+                mCallback = callback;
+                if (mPendingGrantTrustTask != null) {
+                    mPendingGrantTrustTask.run();
+                    mPendingGrantTrustTask = null;
+                }
+            }
         }
     }
 
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..bc65280 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() {}
@@ -32,7 +46,8 @@
 
         /**
          * Score positively voices that exactly match the given locale
-         * @param locale Reference locale. If null, the default locale will be used.
+         * @param locale Reference locale. If null, the system default locale for the
+         * current user will be used ({@link Locale#getDefault()}).
          */
         public ExactLocaleMatcher(Locale locale) {
             if (locale == null) {
@@ -56,7 +71,8 @@
 
         /**
          * Score positively voices with similar locale.
-         * @param locale Reference locale. If null, default will be used.
+         * @param locale Reference locale.  If null, the system default locale for the
+         * current user will be used ({@link Locale#getDefault()}).
          */
         public LanguageMatcher(Locale locale) {
             if (locale == null) {
@@ -150,10 +166,10 @@
     }
 
     /**
-     * Get highest quality voice for the default locale.
+     * Get highest quality voice for the TTS default locale.
      *
      * Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with
-     * {@link LanguageMatcher} set to device default locale.
+     * {@link LanguageMatcher} set to the {@link EngineStatus#getDefaultLocale()}.
      *
      * @param engineStatus
      *            Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
@@ -165,7 +181,7 @@
     public static RequestConfig highestQuality(EngineStatus engineStatus,
             boolean hasToBeEmbedded) {
         return highestQuality(engineStatus, hasToBeEmbedded,
-                new LanguageMatcher(Locale.getDefault()));
+                new LanguageMatcher(engineStatus.getDefaultLocale()));
     }
 
 }
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..938458c9 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;
     }
 
     /**
@@ -135,9 +167,7 @@
         }
     };
 
-    /**
-     * @hide
-     */
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
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..f726743 100644
--- a/core/java/android/speech/tts/TextToSpeechClient.java
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -42,6 +42,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -71,7 +72,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();
@@ -358,9 +358,13 @@
         /** Name of the TTS engine package */
         private final String mPackageName;
 
-        private EngineStatus(String packageName, List<VoiceInfo> voices) {
+        /** Engine default locale */
+        private final Locale mDefaultLocale;
+
+        private EngineStatus(String packageName, List<VoiceInfo> voices, Locale defaultLocale) {
             this.mVoices =  Collections.unmodifiableList(voices);
             this.mPackageName = packageName;
+            this.mDefaultLocale = defaultLocale;
         }
 
         /**
@@ -376,6 +380,16 @@
         public String getEnginePackage() {
             return mPackageName;
         }
+
+        /**
+         * Get the default locale to use for TTS with this TTS engine.
+         * Unless the user changed the TTS settings for this engine, the value returned should be
+         * the same as the system default locale for the current user
+         * ({@link Locale#getDefault()}).
+         */
+        public Locale getDefaultLocale() {
+            return mDefaultLocale;
+        }
     }
 
     /** Unique synthesis request identifier. */
@@ -513,7 +527,6 @@
         }
     }
 
-
     /**
      * Connects the client to TTS service. This method returns immediately, and connects to the
      * service in the background.
@@ -640,7 +653,9 @@
             return null;
         }
 
-        return new EngineStatus(mServiceConnection.getEngineName(), voices);
+        return new EngineStatus(mServiceConnection.getEngineName(), voices,
+                mEnginesHelper.getLocalePrefForEngine(
+                        mServiceConnection.getEngineName()));
     }
 
     private class Connection implements ServiceConnection {
@@ -698,7 +713,9 @@
             public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) {
                 synchronized (mLock) {
                     mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(),
-                            voicesInfo);
+                            voicesInfo,
+                            mEnginesHelper.getLocalePrefForEngine(
+                                    mServiceConnection.getEngineName()));
                     mMainHandler.obtainMessage(InternalHandler.WHAT_ENGINE_STATUS_CHANGED,
                             mEngineStatus).sendToTarget();
                 }
@@ -795,15 +812,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 +839,7 @@
         }
     }
 
-    private abstract class Action {
+    private abstract class Action<T> {
         private final String mName;
 
         public Action(String name) {
@@ -831,7 +847,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 +871,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 +892,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 +907,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 +918,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 +957,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 +986,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 +1027,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 +1035,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 +1044,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 +1056,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 +1084,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 +1102,7 @@
                 if (queueResult != Status.SUCCESS) {
                     removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
                 }
+                return null;
             }
         });
     }
@@ -1039,9 +1126,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 +1144,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..20f3ad7 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) {
@@ -442,8 +460,8 @@
     }
 
     private String[] getSettingsLocale() {
-        final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
-        return TtsEngines.parseLocalePref(locale);
+        final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
+        return TtsEngines.toOldLocaleStringFormat(locale);
     }
 
     private int getSecureSettingInt(String name, int defaultValue) {
@@ -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/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index 9b929a3..7474efe 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+
 import static android.provider.Settings.Secure.getString;
 
 import android.provider.Settings;
@@ -42,8 +43,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.MissingResourceException;
 
 /**
@@ -53,16 +56,52 @@
  * Comments in this class the use the shorthand "system engines" for engines that
  * are a part of the system image.
  *
+ * This class is thread-safe/
+ *
  * @hide
  */
 public class TtsEngines {
     private static final String TAG = "TtsEngines";
     private static final boolean DBG = false;
 
-    private static final String LOCALE_DELIMITER = "-";
+    /** Locale delimiter used by the old-style 3 char locale string format (like "eng-usa") */
+    private static final String LOCALE_DELIMITER_OLD = "-";
+
+    /** Locale delimiter used by the new-style locale string format (Locale.toString() results,
+     * like "en_US") */
+    private static final String LOCALE_DELIMITER_NEW = "_";
 
     private final Context mContext;
 
+    /** Mapping of various language strings to the normalized Locale form */
+    private static final Map<String, String> sNormalizeLanguage;
+
+    /** Mapping of various country strings to the normalized Locale form */
+    private static final Map<String, String> sNormalizeCountry;
+
+    // Populate the sNormalize* maps
+    static {
+        HashMap<String, String> normalizeLanguage = new HashMap<String, String>();
+        for (String language : Locale.getISOLanguages()) {
+            try {
+                normalizeLanguage.put(new Locale(language).getISO3Language(), language);
+            } catch (MissingResourceException e) {
+                continue;
+            }
+        }
+        sNormalizeLanguage = Collections.unmodifiableMap(normalizeLanguage);
+
+        HashMap<String, String> normalizeCountry = new HashMap<String, String>();
+        for (String country : Locale.getISOCountries()) {
+            try {
+                normalizeCountry.put(new Locale("", country).getISO3Country(), country);
+            } catch (MissingResourceException e) {
+                continue;
+            }
+        }
+        sNormalizeCountry = Collections.unmodifiableMap(normalizeCountry);
+    }
+
     public TtsEngines(Context ctx) {
         mContext = ctx;
     }
@@ -282,139 +321,139 @@
     }
 
     /**
-     * Returns the locale string for a given TTS engine. Attempts to read the
+     * Returns the default locale for a given TTS engine. Attempts to read the
      * value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
-     * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
-     * both these values are empty, the default phone locale is returned.
+     * default phone locale is returned.
      *
      * @param engineName the engine to return the locale for.
-     * @return the locale string preference for this engine. Will be non null
-     *         and non empty.
+     * @return the locale preference for this engine. Will be non null.
      */
-    public String getLocalePrefForEngine(String engineName) {
-        String locale = parseEnginePrefFromList(
-                getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
-                engineName);
-
-        if (TextUtils.isEmpty(locale)) {
-            // The new style setting is unset, attempt to return the old style setting.
-            locale = getV1Locale();
-        }
-
-        if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + locale);
-
-        return locale;
+    public Locale getLocalePrefForEngine(String engineName) {
+        return getLocalePrefForEngine(engineName,
+                getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE));
     }
 
     /**
+     * Returns the default locale for a given TTS engine from given settings string. */
+    public Locale getLocalePrefForEngine(String engineName, String prefValue) {
+        String localeString = parseEnginePrefFromList(
+                prefValue,
+                engineName);
+
+        if (TextUtils.isEmpty(localeString)) {
+            // The new style setting is unset, attempt to return the old style setting.
+            return Locale.getDefault();
+        }
+
+        Locale result = parseLocaleString(localeString);
+        if (result == null) {
+            Log.w(TAG, "Failed to parse locale " + localeString + ", returning en_US instead");
+            result = Locale.US;
+        }
+
+        if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + result);
+
+        return result;
+    }
+
+
+    /**
      * True if a given TTS engine uses the default phone locale as a default locale. Attempts to
-     * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
-     * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
-     * both these values are empty, this methods returns true.
+     * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}. If
+     * its  value is empty, this methods returns true.
      *
      * @param engineName the engine to return the locale for.
      */
     public boolean isLocaleSetToDefaultForEngine(String engineName) {
-        return (TextUtils.isEmpty(parseEnginePrefFromList(
+        return TextUtils.isEmpty(parseEnginePrefFromList(
                     getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
-                    engineName)) &&
-                    TextUtils.isEmpty(
-                        Settings.Secure.getString(mContext.getContentResolver(),
-                        Settings.Secure.TTS_DEFAULT_LANG)));
+                    engineName));
     }
 
-
     /**
-     * Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}.
-     * Varies from {@link String#split} in that it will always return an array
-     * of length 3 with non null values.
+     * Parses a locale encoded as a string, and tries its best to return a valid {@link Locale}
+     * object, even if the input string is encoded using the old-style 3 character format e.g.
+     * "deu-deu". At the end, we test if the resulting locale can return ISO3 language and
+     * country codes ({@link Locale#getISO3Language()} and {@link Locale#getISO3Country()}),
+     * if it fails to do so, we return null.
      */
-    public static String[] parseLocalePref(String pref) {
-        String[] returnVal = new String[] { "", "", ""};
-        if (!TextUtils.isEmpty(pref)) {
-            String[] split = pref.split(LOCALE_DELIMITER);
-            System.arraycopy(split, 0, returnVal, 0, split.length);
+    public Locale parseLocaleString(String localeString) {
+        String language = "", country = "", variant = "";
+        if (!TextUtils.isEmpty(localeString)) {
+            String[] split = localeString.split(
+                    "[" + LOCALE_DELIMITER_OLD + LOCALE_DELIMITER_NEW + "]");
+            language = split[0].toLowerCase();
+            if (split.length == 0) {
+                Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Only" +
+                            " separators");
+                return null;
+            }
+            if (split.length > 3) {
+                Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Too" +
+                        " many separators");
+                return null;
+            }
+            if (split.length >= 2) {
+                country = split[1].toUpperCase();
+            }
+            if (split.length >= 3) {
+                variant = split[2];
+            }
+
         }
 
-        if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] +
-                "," + returnVal[2] +")");
+        String normalizedLanguage = sNormalizeLanguage.get(language);
+        if (normalizedLanguage != null) {
+            language = normalizedLanguage;
+        }
 
-        return returnVal;
+        String normalizedCountry= sNormalizeCountry.get(country);
+        if (normalizedCountry != null) {
+            country = normalizedCountry;
+        }
+
+        if (DBG) Log.d(TAG, "parseLocalePref(" + language + "," + country +
+                "," + variant +")");
+
+        Locale result = new Locale(language, country, variant);
+        try {
+            result.getISO3Language();
+            result.getISO3Country();
+            return result;
+        } catch(MissingResourceException e) {
+            Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object.");
+            return null;
+        }
     }
 
     /**
-     * @return the old style locale string constructed from
-     *         {@link Settings.Secure#TTS_DEFAULT_LANG},
-     *         {@link Settings.Secure#TTS_DEFAULT_COUNTRY} and
-     *         {@link Settings.Secure#TTS_DEFAULT_VARIANT}. If no such locale is set,
-     *         then return the default phone locale.
-     */
-    private String getV1Locale() {
-        final ContentResolver cr = mContext.getContentResolver();
-
-        final String lang = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_LANG);
-        final String country = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_COUNTRY);
-        final String variant = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_VARIANT);
-
-        if (TextUtils.isEmpty(lang)) {
-            return getDefaultLocale();
-        }
-
-        String v1Locale = lang;
-        if (!TextUtils.isEmpty(country)) {
-            v1Locale += LOCALE_DELIMITER + country;
-        } else {
-            return v1Locale;
-        }
-
-        if (!TextUtils.isEmpty(variant)) {
-            v1Locale += LOCALE_DELIMITER + variant;
-        }
-
-        return v1Locale;
-    }
-
-    /**
-     * Return the default device locale in form of 3 letter codes delimited by
-     * {@link #LOCALE_DELIMITER}:
+     * Return the old-style string form of the locale. It consists of 3 letter codes:
      * <ul>
-     *   <li> "ISO 639-2/T language code" if locale have no country entry</li>
-     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code "
-     *     if locale have no variant entry</li>
-     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code
-     *     {@link #LOCALE_DELIMITER} variant" if locale have variant entry</li>
+     *   <li>"ISO 639-2/T language code" if the locale has no country entry</li>
+     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code"
+     *     if the locale has no variant entry</li>
+     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country
+     *     code{@link #LOCALE_DELIMITER}variant" if the locale has a variant entry</li>
      * </ul>
+     * If we fail to generate those codes using {@link Locale#getISO3Country()} and
+     * {@link Locale#getISO3Language()}, then we return new String[]{"eng","USA",""};
      */
-    public String getDefaultLocale() {
-        final Locale locale = Locale.getDefault();
-
+    static public String[] toOldLocaleStringFormat(Locale locale) {
+        String[] ret = new String[]{"","",""};
         try {
             // Note that the default locale might have an empty variant
             // or language, and we take care that the construction is
             // the same as {@link #getV1Locale} i.e no trailing delimiters
             // or spaces.
-            String defaultLocale = locale.getISO3Language();
-            if (TextUtils.isEmpty(defaultLocale)) {
-                Log.w(TAG, "Default locale is empty.");
-                return "";
-            }
+            ret[0] = locale.getISO3Language();
+            ret[1] = locale.getISO3Country();
+            ret[2] = locale.getVariant();
 
-            if (!TextUtils.isEmpty(locale.getISO3Country())) {
-                defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
-            } else {
-                // Do not allow locales of the form lang--variant with
-                // an empty country.
-                return defaultLocale;
-            }
-            if (!TextUtils.isEmpty(locale.getVariant())) {
-                defaultLocale += LOCALE_DELIMITER + locale.getVariant();
-            }
-
-            return defaultLocale;
+            return ret;
         } catch (MissingResourceException e) {
             // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the
             // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US).
-            return "eng-usa";
+            return new String[]{"eng","USA",""};
         }
     }
 
@@ -443,16 +482,21 @@
         return null;
     }
 
-    public synchronized void updateLocalePrefForEngine(String name, String newLocale) {
+    /**
+     * Serialize the locale to a string and store it as a default locale for the given engine. If
+     * the passed locale is null, an empty string will be serialized; that empty string, when
+     * read back, will evaluate to {@link Locale#getDefault()}.
+     */
+    public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
         final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
                 Settings.Secure.TTS_DEFAULT_LOCALE);
         if (DBG) {
-            Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale +
+            Log.d(TAG, "updateLocalePrefForEngine(" + engineName + ", " + newLocale +
                     "), originally: " + prefList);
         }
 
         final String newPrefList = updateValueInCommaSeparatedList(prefList,
-                name, newLocale);
+                engineName, (newLocale != null) ? newLocale.toString() : "");
 
         if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());
 
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/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index d426d124..8674c66 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -38,7 +38,7 @@
      * {@hide}
      */
     void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, int flags, Paint p);
+            float x, float y, boolean isRtl, Paint p);
 
    /**
      * Just like {@link Paint#measureText}.
@@ -55,12 +55,12 @@
      * @hide
      */
     float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
-            int flags, float[] advances, int advancesIndex, Paint paint);
+            boolean isRtl, float[] advances, int advancesIndex, Paint paint);
 
     /**
      * Just like {@link Paint#getTextRunCursor}.
      * @hide
      */
-    int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+    int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
             int cursorOpt, Paint p);
 }
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index f8e3c83..2415b11 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -159,18 +159,17 @@
         mPos = p + len;
 
         if (mEasy) {
-            int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
-                ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
-            return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
+            boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
+            return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
         }
 
         float totalAdvance = 0;
         int level = mLevels[p];
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
-                int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+                boolean isRtl = (level & 0x1) != 0;
                 totalAdvance +=
-                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
+                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 if (i == e) {
                     break;
                 }
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 1d9aa05..6b984d6 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -503,7 +503,7 @@
         // Span watchers need to be called after text watchers, which may update the layout
         sendToSpanWatchers(start, end, newLen - origLen);
 
-        return this; 
+        return this;
     }
 
     private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
@@ -745,7 +745,7 @@
             }
         }
 
-        return 0; 
+        return 0;
     }
 
     /**
@@ -1117,20 +1117,20 @@
      * {@hide}
      */
     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, int flags, Paint p) {
+            float x, float y, boolean isRtl, Paint p) {
         checkRange("drawTextRun", start, end);
 
         int contextLen = contextEnd - contextStart;
         int len = end - start;
         if (contextEnd <= mGapStart) {
-            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
+            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
         } else if (contextStart >= mGapStart) {
             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
-                    contextLen, x, y, flags, p);
+                    contextLen, x, y, isRtl, p);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
-            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
+            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
             TextUtils.recycle(buf);
         }
     }
@@ -1187,7 +1187,7 @@
      * Don't call this yourself -- exists for Paint to use internally.
      * {@hide}
      */
-    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
             float[] advances, int advancesPos, Paint p) {
 
         float ret;
@@ -1197,15 +1197,15 @@
 
         if (end <= mGapStart) {
             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
-                    flags, advances, advancesPos);
+                    isRtl, advances, advancesPos);
         } else if (start >= mGapStart) {
             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
-                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+                    contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunAdvances(buf, start - contextStart, len,
-                    0, contextLen, flags, advances, advancesPos);
+                    0, contextLen, isRtl, advances, advancesPos);
             TextUtils.recycle(buf);
         }
 
@@ -1228,7 +1228,7 @@
      *
      * @param contextStart the start index of the context
      * @param contextEnd the (non-inclusive) end index of the context
-     * @param flags either DIRECTION_RTL or DIRECTION_LTR
+     * @param dir either DIRECTION_RTL or DIRECTION_LTR
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
@@ -1238,7 +1238,7 @@
      * @deprecated This is an internal method, refrain from using it in your code
      */
     @Deprecated
-    public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+    public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
             int cursorOpt, Paint p) {
 
         int ret;
@@ -1246,15 +1246,15 @@
         int contextLen = contextEnd - contextStart;
         if (contextEnd <= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart, contextLen,
-                    flags, offset, cursorOpt);
+                    dir, offset, cursorOpt);
         } else if (contextStart >= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
-                    flags, offset + mGapLength, cursorOpt) - mGapLength;
+                    dir, offset + mGapLength, cursorOpt) - mGapLength;
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunCursor(buf, 0, contextLen,
-                    flags, offset - contextStart, cursorOpt) + contextStart;
+                    dir, offset - contextStart, cursorOpt) + contextStart;
             TextUtils.recycle(buf);
         }
 
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index d892f19..c19cf32 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -664,14 +664,14 @@
             }
         }
 
-        int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
+        int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
         int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
         if (mCharsValid) {
             return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
-                    flags, offset, cursorOpt);
+                    dir, offset, cursorOpt);
         } else {
             return wp.getTextRunCursor(mText, mStart + spanStart,
-                    mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
+                    mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
         }
     }
 
@@ -738,15 +738,14 @@
 
         int contextLen = contextEnd - contextStart;
         if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
-            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
             if (mCharsValid) {
                 ret = wp.getTextRunAdvances(mChars, start, runLen,
-                        contextStart, contextLen, flags, null, 0);
+                        contextStart, contextLen, runIsRtl, null, 0);
             } else {
                 int delta = mStart;
                 ret = wp.getTextRunAdvances(mText, delta + start,
                         delta + end, delta + contextStart, delta + contextEnd,
-                        flags, null, 0);
+                        runIsRtl, null, 0);
             }
         }
 
@@ -977,16 +976,15 @@
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
 
-        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start, count, contextStart, contextCount,
-                    x, y, flags, wp);
+                    x, y, runIsRtl, wp);
         } else {
             int delta = mStart;
             c.drawTextRun(mText, delta + start, delta + end,
-                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
+                    delta + contextStart, delta + contextEnd, x, y, runIsRtl, wp);
         }
     }
 
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/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index d1f35dd..9fec9a1 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -195,7 +195,7 @@
      * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
      */
     public static String getBestDateTimePattern(Locale locale, String skeleton) {
-        return ICU.getBestDateTimePattern(skeleton, locale.toString());
+        return ICU.getBestDateTimePattern(skeleton, locale);
     }
 
     /**
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/ChangeImageTransform.java b/core/java/android/transition/ChangeImageTransform.java
new file mode 100644
index 0000000..b003690
--- /dev/null
+++ b/core/java/android/transition/ChangeImageTransform.java
@@ -0,0 +1,234 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.util.Map;
+
+/**
+ * Transitions changes in ImageView {@link ImageView#setScaleType(ImageView.ScaleType)} as
+ * well as image scaling due to ImageView size changes. When combined with
+ * {@link android.transition.ChangeBounds}, an ImageView that changes size will
+ * scale smoothly.
+ */
+public class ChangeImageTransform extends Transition {
+
+    private static final String TAG = "ChangeScaleType";
+
+    private static final String PROPNAME_MATRIX = "android:changeScaleType:matrix";
+    private static final String PROPNAME_BOUNDS = "android:changeScaleType:bounds";
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_MATRIX,
+            PROPNAME_BOUNDS,
+    };
+
+    private static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
+        @Override
+        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+            return null;
+        }
+    };
+
+    private static Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY
+            = new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
+        @Override
+        public void set(ImageView object, Matrix value) {
+            object.animateTransform(value);
+        }
+
+        @Override
+        public Matrix get(ImageView object) {
+            return null;
+        }
+    };
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
+            return;
+        }
+        ImageView imageView = (ImageView) view;
+        Drawable drawable = imageView.getDrawable();
+        if (drawable == null) {
+            return;
+        }
+        Map<String, Object> values = transitionValues.values;
+
+        int left = view.getLeft();
+        int top = view.getTop();
+        int right = view.getRight();
+        int bottom = view.getBottom();
+
+        Rect bounds = new Rect(left, top, right, bottom);
+        values.put(PROPNAME_BOUNDS, bounds);
+        Matrix matrix;
+        ImageView.ScaleType scaleType = imageView.getScaleType();
+        if (scaleType == ImageView.ScaleType.FIT_XY) {
+            matrix = imageView.getImageMatrix();
+            if (!matrix.isIdentity()) {
+                matrix = new Matrix(matrix);
+            } else {
+                int drawableWidth = drawable.getIntrinsicWidth();
+                int drawableHeight = drawable.getIntrinsicHeight();
+                if (drawableWidth > 0 && drawableHeight > 0) {
+                    float scaleX = ((float) bounds.width()) / drawableWidth;
+                    float scaleY = ((float) bounds.height()) / drawableHeight;
+                    matrix = new Matrix();
+                    matrix.setScale(scaleX, scaleY);
+                } else {
+                    matrix = null;
+                }
+            }
+        } else {
+            matrix = new Matrix(imageView.getImageMatrix());
+        }
+        values.put(PROPNAME_MATRIX, matrix);
+    }
+
+    @Override
+    public void captureStartValues(TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    /**
+     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
+     * {@link android.widget.ImageView.ScaleType}.
+     *
+     * @param sceneRoot   The root of the transition hierarchy.
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return An Animator to move an ImageView or null if the View is not an ImageView,
+     * the Drawable changed, the View is not VISIBLE, or there was no change.
+     */
+    @Override
+    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null) {
+            return null;
+        }
+        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        if (startBounds == null || endBounds == null) {
+            return null;
+        }
+
+        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+        boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
+                (startMatrix != null && startMatrix.equals(endMatrix));
+
+        if (startBounds.equals(endBounds) && matricesEqual) {
+            return null;
+        }
+
+        ImageView imageView = (ImageView) endValues.view;
+        Drawable drawable = imageView.getDrawable();
+        int drawableWidth = drawable.getIntrinsicWidth();
+        int drawableHeight = drawable.getIntrinsicHeight();
+
+        ObjectAnimator animator;
+        if (drawableWidth == 0 || drawableHeight == 0) {
+            animator = createNullAnimator(imageView);
+        } else {
+            if (startMatrix == null) {
+                startMatrix = Matrix.IDENTITY_MATRIX;
+            }
+            if (endMatrix == null) {
+                endMatrix = Matrix.IDENTITY_MATRIX;
+            }
+            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
+        }
+        return animator;
+    }
+
+    private ObjectAnimator createNullAnimator(ImageView imageView) {
+        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
+                NULL_MATRIX_EVALUATOR, null, null);
+    }
+
+    private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
+            final Matrix endMatrix) {
+        ObjectAnimator animator = ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
+                new MatrixEvaluator(), startMatrix, endMatrix);
+        /*
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            private Matrix mPausedMatrix;
+
+            @Override
+            public void onAnimationPause(Animator animation) {
+                if (mPausedMatrix == null) {
+                    mPausedMatrix = new Matrix();
+                }
+                Matrix imageMatrix = imageView.getImageMatrix();
+                mPausedMatrix.set(imageMatrix);
+                imageView.animateTransform(endMatrix);
+            }
+
+            @Override
+            public void onAnimationResume(Animator animation) {
+                imageView.animateTransform(mPausedMatrix);
+            }
+        };
+        animator.addPauseListener(listener);
+        */
+        return animator;
+    }
+
+    private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
+
+        float[] mTempStartValues = new float[9];
+
+        float[] mTempEndValues = new float[9];
+
+        Matrix mTempMatrix = new Matrix();
+
+        @Override
+        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+            startValue.getValues(mTempStartValues);
+            endValue.getValues(mTempEndValues);
+            for (int i = 0; i < 9; i++) {
+                float diff = mTempEndValues[i] - mTempStartValues[i];
+                mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
+            }
+            mTempMatrix.setValues(mTempEndValues);
+            return mTempMatrix;
+        }
+    }
+
+}
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
index fae527c..feb8efd 100644
--- a/core/java/android/transition/Explode.java
+++ b/core/java/android/transition/Explode.java
@@ -15,20 +15,16 @@
  */
 package android.transition;
 
+import com.android.internal.R;
+
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.graphics.Path;
 import android.graphics.Rect;
 import android.util.FloatMath;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-
 /**
  * This transition tracks changes to the visibility of target views in the
  * start and end scenes and moves views in or out from the edges of the
@@ -44,8 +40,7 @@
     private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
     private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
     private static final String TAG = "Explode";
-
-    private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds";
+    private static final String PROPNAME_SCREEN_BOUNDS = "android:explode:screenBounds";
 
     private int[] mTempLoc = new int[2];
 
@@ -56,8 +51,8 @@
     private void captureValues(TransitionValues transitionValues) {
         View view = transitionValues.view;
         view.getLocationOnScreen(mTempLoc);
-        int left = mTempLoc[0] + Math.round(view.getTranslationX());
-        int top = mTempLoc[1] + Math.round(view.getTranslationY());
+        int left = mTempLoc[0];
+        int top = mTempLoc[1];
         int right = left + view.getWidth();
         int bottom = top + view.getHeight();
         transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
@@ -75,27 +70,6 @@
         captureValues(transitionValues);
     }
 
-    private Animator createAnimation(final View view, float startX, float startY, float endX,
-            float endY, float terminalX, float terminalY, TimeInterpolator interpolator) {
-        view.setTranslationX(startX);
-        view.setTranslationY(startY);
-        if (startY == endY && startX == endX) {
-            return null;
-        }
-        Path path = new Path();
-        path.moveTo(startX, startY);
-        path.lineTo(endX, endY);
-        ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
-                View.TRANSLATION_Y, path);
-        pathAnimator.setInterpolator(interpolator);
-        OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY,
-                endX, endY);
-        pathAnimator.addListener(listener);
-        pathAnimator.addPauseListener(listener);
-
-        return pathAnimator;
-    }
-
     @Override
     public Animator onAppear(ViewGroup sceneRoot, View view,
             TransitionValues startValues, TransitionValues endValues) {
@@ -103,29 +77,43 @@
             return null;
         }
         Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        float endX = view.getTranslationX();
+        float endY = view.getTranslationY();
         calculateOut(sceneRoot, bounds, mTempLoc);
+        float startX = endX + mTempLoc[0];
+        float startY = endY + mTempLoc[1];
 
-        final float endX = view.getTranslationX();
-        final float startX = endX + mTempLoc[0];
-        final float endY = view.getTranslationY();
-        final float startY = endY + mTempLoc[1];
-
-        return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate);
+        return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top,
+                startX, startY, endX, endY, sDecelerate);
     }
 
     @Override
     public Animator onDisappear(ViewGroup sceneRoot, View view,
             TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null) {
+            return null;
+        }
         Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        int viewPosX = bounds.left;
+        int viewPosY = bounds.top;
+        float startX = view.getTranslationX();
+        float startY = view.getTranslationY();
+        float endX = startX;
+        float endY = startY;
+        int[] interruptedPosition = (int[]) startValues.view.getTag(R.id.transitionPosition);
+        if (interruptedPosition != null) {
+            // We want to have the end position relative to the interrupted position, not
+            // the position it was supposed to start at.
+            endX += interruptedPosition[0] - bounds.left;
+            endY += interruptedPosition[1] - bounds.top;
+            bounds.offsetTo(interruptedPosition[0], interruptedPosition[1]);
+        }
         calculateOut(sceneRoot, bounds, mTempLoc);
+        endX += mTempLoc[0];
+        endY += mTempLoc[1];
 
-        final float startX = view.getTranslationX();
-        final float endX = startX + mTempLoc[0];
-        final float startY = view.getTranslationY();
-        final float endY = startY + mTempLoc[1];
-
-        return createAnimation(view, startX, startY, endX, endY, startX, startY,
-                sAccelerate);
+        return TranslationAnimationCreator.createAnimation(view, startValues,
+                viewPosX, viewPosY, startX, startY, endX, endY, sAccelerate);
     }
 
     private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
@@ -153,8 +141,8 @@
 
         if (xVector == 0 && yVector == 0) {
             // Random direction when View is centered on focal View.
-            xVector = (float)(Math.random() * 2) - 1;
-            yVector = (float)(Math.random() * 2) - 1;
+            xVector = (float) (Math.random() * 2) - 1;
+            yVector = (float) (Math.random() * 2) - 1;
         }
         float vectorSize = calculateDistance(xVector, yVector);
         xVector /= vectorSize;
@@ -176,53 +164,4 @@
     private static float calculateDistance(float x, float y) {
         return FloatMath.sqrt((x * x) + (y * y));
     }
-
-    private static class OutAnimatorListener extends AnimatorListenerAdapter {
-        private final View mView;
-        private boolean mCanceled = false;
-        private float mPausedX;
-        private float mPausedY;
-        private final float mTerminalX;
-        private final float mTerminalY;
-        private final float mEndX;
-        private final float mEndY;
-
-        public OutAnimatorListener(View view, float terminalX, float terminalY,
-                float endX, float endY) {
-            mView = view;
-            mTerminalX = terminalX;
-            mTerminalY = terminalY;
-            mEndX = endX;
-            mEndY = endY;
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animator) {
-            mView.setTranslationX(mTerminalX);
-            mView.setTranslationY(mTerminalY);
-            mCanceled = true;
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animator) {
-            if (!mCanceled) {
-                mView.setTranslationX(mTerminalX);
-                mView.setTranslationY(mTerminalY);
-            }
-        }
-
-        @Override
-        public void onAnimationPause(Animator animator) {
-            mPausedX = mView.getTranslationX();
-            mPausedY = mView.getTranslationY();
-            mView.setTranslationY(mEndX);
-            mView.setTranslationY(mEndY);
-        }
-
-        @Override
-        public void onAnimationResume(Animator animator) {
-            mView.setTranslationX(mPausedX);
-            mView.setTranslationY(mPausedY);
-        }
-    }
 }
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index e70dc0c..71559da 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -63,23 +63,23 @@
     /**
      * Fading mode used in {@link #Fade(int)} to make the transition
      * operate on targets that are appearing. Maybe be combined with
-     * {@link #OUT} to fade both in and out.
+     * {@link #OUT} to fade both in and out. Equivalent to
+     * {@link Visibility#IN}.
      */
-    public static final int IN = 0x1;
+    public static final int IN = Visibility.IN;
+
     /**
      * Fading mode used in {@link #Fade(int)} to make the transition
      * operate on targets that are disappearing. Maybe be combined with
-     * {@link #IN} to fade both in and out.
+     * {@link #IN} to fade both in and out. Equivalent to
+     * {@link Visibility#OUT}.
      */
-    public static final int OUT = 0x2;
-
-    private int mFadingMode;
+    public static final int OUT = Visibility.OUT;
 
     /**
      * Constructs a Fade transition that will fade targets in and out.
      */
     public Fade() {
-        this(IN | OUT);
     }
 
     /**
@@ -90,7 +90,7 @@
      * {@link #IN} and {@link #OUT}.
      */
     public Fade(int fadingMode) {
-        mFadingMode = fadingMode;
+        setMode(fadingMode);
     }
 
     /**
@@ -115,9 +115,6 @@
     public Animator onAppear(ViewGroup sceneRoot, View view,
             TransitionValues startValues,
             TransitionValues endValues) {
-        if ((mFadingMode & IN) != IN || endValues == null) {
-            return null;
-        }
         if (DBG) {
             View startView = (startValues != null) ? startValues.view : null;
             Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
@@ -129,10 +126,6 @@
     @Override
     public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
             TransitionValues endValues) {
-        if ((mFadingMode & OUT) != OUT) {
-            return null;
-        }
-
         return createAnimation(view, 1, 0);
     }
 
diff --git a/core/java/android/transition/MatrixClippedDrawable.java b/core/java/android/transition/MatrixClippedDrawable.java
deleted file mode 100644
index ebaad59..0000000
--- a/core/java/android/transition/MatrixClippedDrawable.java
+++ /dev/null
@@ -1,300 +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 android.transition;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.Property;
-
-/**
- * Used in MoveImage to mock an ImageView as a Drawable to be scaled in the scene root Overlay.
- * @hide
- */
-class MatrixClippedDrawable extends Drawable implements Drawable.Callback {
-    private static final String TAG = "MatrixClippedDrawable";
-
-    private ClippedMatrixState mClippedMatrixState;
-
-    public static final Property<MatrixClippedDrawable, Rect> CLIP_PROPERTY
-            = new Property<MatrixClippedDrawable, Rect>(Rect.class, "clipRect") {
-
-        @Override
-        public Rect get(MatrixClippedDrawable object) {
-            return object.getClipRect();
-        }
-
-        @Override
-        public void set(MatrixClippedDrawable object, Rect value) {
-            object.setClipRect(value);
-        }
-    };
-
-    public static final Property<MatrixClippedDrawable, Matrix> MATRIX_PROPERTY
-            = new Property<MatrixClippedDrawable, Matrix>(Matrix.class, "matrix") {
-        @Override
-        public void set(MatrixClippedDrawable object, Matrix value) {
-            object.setMatrix(value);
-        }
-
-        @Override
-        public Matrix get(MatrixClippedDrawable object) {
-            return object.getMatrix();
-        }
-    };
-
-    public MatrixClippedDrawable(Drawable drawable) {
-        this(null, null);
-
-        mClippedMatrixState.mDrawable = drawable;
-
-        if (drawable != null) {
-            drawable.setCallback(this);
-        }
-    }
-
-    public void setMatrix(Matrix matrix) {
-        if (matrix == null) {
-            mClippedMatrixState.mMatrix = null;
-        } else {
-            if (mClippedMatrixState.mMatrix == null) {
-                mClippedMatrixState.mMatrix = new Matrix();
-            }
-            mClippedMatrixState.mMatrix.set(matrix);
-        }
-        invalidateSelf();
-    }
-
-    public Matrix getMatrix() {
-        return mClippedMatrixState.mMatrix;
-    }
-
-    public Rect getClipRect() {
-        return mClippedMatrixState.mClipRect;
-    }
-
-    public void setClipRect(Rect clipRect) {
-        if (clipRect == null) {
-            if (mClippedMatrixState.mClipRect != null) {
-                mClippedMatrixState.mClipRect = null;
-                invalidateSelf();
-            }
-        } else {
-            if (mClippedMatrixState.mClipRect == null) {
-                mClippedMatrixState.mClipRect = new Rect(clipRect);
-            } else {
-                mClippedMatrixState.mClipRect.set(clipRect);
-            }
-            invalidateSelf();
-        }
-    }
-
-    // overrides from Drawable.Callback
-
-    public void invalidateDrawable(Drawable who) {
-        final Drawable.Callback callback = getCallback();
-        if (callback != null) {
-            callback.invalidateDrawable(this);
-        }
-    }
-
-    public void scheduleDrawable(Drawable who, Runnable what, long when) {
-        final Drawable.Callback callback = getCallback();
-        if (callback != null) {
-            callback.scheduleDrawable(this, what, when);
-        }
-    }
-
-    public void unscheduleDrawable(Drawable who, Runnable what) {
-        final Drawable.Callback callback = getCallback();
-        if (callback != null) {
-            callback.unscheduleDrawable(this, what);
-        }
-    }
-
-    // overrides from Drawable
-
-    @Override
-    public int getChangingConfigurations() {
-        return super.getChangingConfigurations()
-                | mClippedMatrixState.mChangingConfigurations
-                | mClippedMatrixState.mDrawable.getChangingConfigurations();
-    }
-
-    @Override
-    public boolean getPadding(Rect padding) {
-        // XXX need to adjust padding!
-        return mClippedMatrixState.mDrawable.getPadding(padding);
-    }
-
-    @Override
-    public boolean setVisible(boolean visible, boolean restart) {
-        mClippedMatrixState.mDrawable.setVisible(visible, restart);
-        return super.setVisible(visible, restart);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mClippedMatrixState.mDrawable.setAlpha(alpha);
-    }
-
-    @Override
-    public int getAlpha() {
-        return mClippedMatrixState.mDrawable.getAlpha();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mClippedMatrixState.mDrawable.setColorFilter(cf);
-    }
-
-    @Override
-    public int getOpacity() {
-        return mClippedMatrixState.mDrawable.getOpacity();
-    }
-
-    @Override
-    public boolean isStateful() {
-        return mClippedMatrixState.mDrawable.isStateful();
-    }
-
-    @Override
-    protected boolean onStateChange(int[] state) {
-        return mClippedMatrixState.mDrawable.setState(state);
-    }
-
-    @Override
-    protected boolean onLevelChange(int level) {
-        mClippedMatrixState.mDrawable.setLevel(level);
-        invalidateSelf();
-        return true;
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        super.setBounds(bounds);
-        if (mClippedMatrixState.mMatrix == null) {
-            mClippedMatrixState.mDrawable.setBounds(bounds);
-        } else {
-            int drawableWidth = mClippedMatrixState.mDrawable.getIntrinsicWidth();
-            int drawableHeight = mClippedMatrixState.mDrawable.getIntrinsicHeight();
-            mClippedMatrixState.mDrawable.setBounds(bounds.left, bounds.top,
-                    drawableWidth + bounds.left, drawableHeight + bounds.top);
-        }
-        invalidateSelf();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        Rect bounds = getBounds();
-        int left = bounds.left;
-        int top = bounds.top;
-        int saveCount = canvas.getSaveCount();
-        canvas.save();
-        if (mClippedMatrixState.mClipRect != null) {
-            canvas.clipRect(mClippedMatrixState.mClipRect);
-        } else {
-            canvas.clipRect(bounds);
-        }
-
-        if (mClippedMatrixState != null && !mClippedMatrixState.mMatrix.isIdentity()) {
-            canvas.translate(left, top);
-            canvas.concat(mClippedMatrixState.mMatrix);
-            canvas.translate(-left, -top);
-        }
-        mClippedMatrixState.mDrawable.draw(canvas);
-        canvas.restoreToCount(saveCount);
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mClippedMatrixState.mDrawable.getIntrinsicWidth();
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mClippedMatrixState.mDrawable.getIntrinsicHeight();
-    }
-
-    @Override
-    public Drawable.ConstantState getConstantState() {
-        if (mClippedMatrixState.canConstantState()) {
-            mClippedMatrixState.mChangingConfigurations = getChangingConfigurations();
-            return mClippedMatrixState;
-        }
-        return null;
-    }
-
-    final static class ClippedMatrixState extends Drawable.ConstantState {
-        Drawable mDrawable;
-        Matrix mMatrix;
-        Rect mClipRect;
-
-        private boolean mCheckedConstantState;
-        private boolean mCanConstantState;
-        int mChangingConfigurations;
-
-        ClippedMatrixState(ClippedMatrixState orig, MatrixClippedDrawable owner, Resources res) {
-            if (orig != null) {
-                if (res != null) {
-                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
-                } else {
-                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
-                }
-                mDrawable.setCallback(owner);
-                mCheckedConstantState = mCanConstantState = true;
-                if (orig.mMatrix != null) {
-                    mMatrix = new Matrix(orig.mMatrix);
-                }
-                if (orig.mClipRect != null) {
-                    mClipRect = new Rect(orig.mClipRect);
-                }
-            }
-        }
-
-        @Override
-        public Drawable newDrawable() {
-            return new MatrixClippedDrawable(this, null);
-        }
-
-        @Override
-        public Drawable newDrawable(Resources res) {
-            return new MatrixClippedDrawable(this, res);
-        }
-
-        @Override
-        public int getChangingConfigurations() {
-            return mChangingConfigurations;
-        }
-
-        boolean canConstantState() {
-            if (!mCheckedConstantState) {
-                mCanConstantState = mDrawable.getConstantState() != null;
-                mCheckedConstantState = true;
-            }
-
-            return mCanConstantState;
-        }
-    }
-
-    private MatrixClippedDrawable(ClippedMatrixState state, Resources res) {
-        mClippedMatrixState = new ClippedMatrixState(state, this, res);
-    }
-
-}
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
index 6f1b6f7..48b96ec 100644
--- a/core/java/android/transition/MoveImage.java
+++ b/core/java/android/transition/MoveImage.java
@@ -15,340 +15,17 @@
  */
 package android.transition;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.animation.ValueAnimator;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.FloatMath;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
-import android.view.ViewParent;
-import android.widget.ImageView;
-
-import java.util.ArrayList;
-import java.util.Map;
-
 /**
- * Transitions ImageViews, including size, scaleType, and matrix. The ImageView drawable
- * must remain the same between both start and end states, but the
- * {@link ImageView#setScaleType(android.widget.ImageView.ScaleType)} may
- * differ.
+ * TO BE REMOVED.
+ * Use ChangeImageTransform + ChangeBounds instead.
+ * @hide
  */
-public class MoveImage extends Transition {
-    private static final String TAG = "MoveImage";
-    private static final String PROPNAME_MATRIX = "android:moveImage:matrix";
-    private static final String PROPNAME_BOUNDS = "android:moveImage:bounds";
-    private static final String PROPNAME_CLIP = "android:moveImage:clip";
-    private static final String PROPNAME_DRAWABLE = "android:moveImage:drawable";
+public class MoveImage extends TransitionSet {
 
-    private int[] mTempLoc = new int[2];
-
-    private static final String[] sTransitionProperties = {
-            PROPNAME_MATRIX,
-            PROPNAME_BOUNDS,
-            PROPNAME_CLIP,
-            PROPNAME_DRAWABLE,
-    };
-
-    private void captureValues(TransitionValues transitionValues) {
-        View view = transitionValues.view;
-        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
-            return;
-        }
-        ImageView imageView = (ImageView) view;
-        Drawable drawable = imageView.getDrawable();
-        if (drawable == null) {
-            return;
-        }
-        Map<String, Object> values = transitionValues.values;
-        values.put(PROPNAME_DRAWABLE, drawable);
-
-        ViewGroup parent = (ViewGroup) view.getParent();
-        parent.getLocationInWindow(mTempLoc);
-        int paddingLeft = view.getPaddingLeft();
-        int paddingTop = view.getPaddingTop();
-        int paddingRight = view.getPaddingRight();
-        int paddingBottom = view.getPaddingBottom();
-        int left = mTempLoc[0] + paddingLeft + view.getLeft() + Math.round(view.getTranslationX());
-        int top = mTempLoc[1] + paddingTop + view.getTop() + Math.round(view.getTranslationY());
-        int right = left + view.getWidth() - paddingRight - paddingLeft;
-        int bottom = top + view.getHeight() - paddingTop - paddingBottom;
-
-        Rect bounds = new Rect(left, top, right, bottom);
-        values.put(PROPNAME_BOUNDS, bounds);
-        Matrix matrix = getMatrix(imageView);
-        values.put(PROPNAME_MATRIX, matrix);
-        values.put(PROPNAME_CLIP, findClip(imageView));
-    }
-
-    @Override
-    public void captureStartValues(TransitionValues transitionValues) {
-        captureValues(transitionValues);
-    }
-
-    @Override
-    public void captureEndValues(TransitionValues transitionValues) {
-        captureValues(transitionValues);
-    }
-
-    @Override
-    public String[] getTransitionProperties() {
-        return sTransitionProperties;
-    }
-
-    /**
-     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
-     * {@link android.widget.ImageView.ScaleType}.
-     * @param sceneRoot The root of the transition hierarchy.
-     * @param startValues The values for a specific target in the start scene.
-     * @param endValues The values for the target in the end scene.
-     * @return An Animator to move an ImageView or null if the View is not an ImageView,
-     * the Drawable changed, the View is not VISIBLE, or there was no change.
-     */
-    @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
-        if (startValues == null || endValues == null
-                || startValues.values.get(PROPNAME_BOUNDS) == null
-                || endValues.values.get(PROPNAME_BOUNDS) == null
-                || startValues.values.get(PROPNAME_DRAWABLE)
-                        != endValues.values.get(PROPNAME_DRAWABLE)) {
-            return null;
-        }
-        ArrayList<PropertyValuesHolder> changes = new ArrayList<PropertyValuesHolder>();
-
-        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
-        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
-
-        if (startMatrix != null && !startMatrix.equals(endMatrix)) {
-            changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY,
-                    new MatrixEvaluator(), startMatrix, endMatrix));
-        }
-
-        sceneRoot.getLocationInWindow(mTempLoc);
-        int rootX = mTempLoc[0];
-        int rootY = mTempLoc[1];
-        final ImageView imageView = (ImageView) endValues.view;
-
-        Drawable drawable = imageView.getDrawable();
-
-        Rect startBounds = new Rect((Rect) startValues.values.get(PROPNAME_BOUNDS));
-        Rect endBounds = new Rect((Rect) endValues.values.get(PROPNAME_BOUNDS));
-        startBounds.offset(-rootX, -rootY);
-        endBounds.offset(-rootX, -rootY);
-
-        if (!startBounds.equals(endBounds)) {
-            changes.add(PropertyValuesHolder.ofObject("bounds", new RectEvaluator(new Rect()),
-                    startBounds, endBounds));
-        }
-
-        Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
-        Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
-        if (startClip != null || endClip != null) {
-            startClip = nonNullClip(startClip, sceneRoot, rootX, rootY);
-            endClip = nonNullClip(endClip, sceneRoot, rootX, rootY);
-
-            expandClip(startBounds, startMatrix, startClip, endClip);
-            expandClip(endBounds, endMatrix, endClip, startClip);
-            boolean clipped = !startClip.contains(startBounds) || !endClip.contains(endBounds);
-            if (!clipped) {
-                startClip = null;
-            } else if (!startClip.equals(endClip)) {
-                changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.CLIP_PROPERTY,
-                        new RectEvaluator(), startClip, endClip));
-            }
-        }
-
-        if (changes.isEmpty()) {
-            return null;
-        }
-
-        drawable = drawable.getConstantState().newDrawable();
-        final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable);
-        final ImageView overlayImage = new ImageView(imageView.getContext());
-        final ViewGroupOverlay overlay = sceneRoot.getOverlay();
-        overlay.add(overlayImage);
-        overlayImage.setLeft(0);
-        overlayImage.setTop(0);
-        overlayImage.setRight(sceneRoot.getWidth());
-        overlayImage.setBottom(sceneRoot.getBottom());
-        overlayImage.setScaleType(ImageView.ScaleType.MATRIX);
-        overlayImage.setImageDrawable(matrixClippedDrawable);
-        matrixClippedDrawable.setMatrix(startMatrix);
-        matrixClippedDrawable.setBounds(startBounds);
-        matrixClippedDrawable.setClipRect(startClip);
-
-        imageView.setVisibility(View.INVISIBLE);
-        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable,
-                changes.toArray(new PropertyValuesHolder[changes.size()]));
-
-        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                imageView.setVisibility(View.VISIBLE);
-                overlay.remove(overlayImage);
-            }
-
-            @Override
-            public void onAnimationPause(Animator animation) {
-                imageView.setVisibility(View.VISIBLE);
-                overlayImage.setVisibility(View.INVISIBLE);
-            }
-
-            @Override
-            public void onAnimationResume(Animator animation) {
-                imageView.setVisibility(View.INVISIBLE);
-                overlayImage.setVisibility(View.VISIBLE);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                onAnimationEnd(animation);
-            }
-        };
-
-        animator.addListener(listener);
-        animator.addPauseListener(listener);
-
-        return animator;
-    }
-
-    private static Rect nonNullClip(Rect clip, ViewGroup sceneRoot, int rootX, int rootY) {
-        if (clip != null) {
-            clip = new Rect(clip);
-            clip.offset(-rootX, -rootY);
-        } else {
-            clip = new Rect(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
-        }
-        return clip;
-    }
-
-    private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) {
-        RectF boundsF = new RectF(bounds);
-        if (matrix != null) {
-            matrix.mapRect(boundsF);
-        }
-        clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left);
-        clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top);
-        clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right);
-        clip.bottom = expandMaxDimension(boundsF.bottom, clip.bottom, otherClip.bottom);
-    }
-
-    private static int expandMinDimension(float boundsDimension, int clipDimension,
-            int otherClipDimension) {
-        if (clipDimension > boundsDimension) {
-            // Already clipped in that dimension, return the clipped value
-            return clipDimension;
-        }
-        return Math.min(clipDimension, otherClipDimension);
-    }
-
-    private static int expandMaxDimension(float boundsDimension, int clipDimension,
-            int otherClipDimension) {
-        return -expandMinDimension(-boundsDimension, -clipDimension, -otherClipDimension);
-    }
-
-    private static Matrix getMatrix(ImageView imageView) {
-        Drawable drawable = imageView.getDrawable();
-        int drawableWidth = drawable.getIntrinsicWidth();
-        int drawableHeight = drawable.getIntrinsicHeight();
-        ImageView.ScaleType scaleType = imageView.getScaleType();
-        Matrix matrix;
-        if (drawableWidth <= 0 || drawableHeight <= 0) {
-            matrix = null;
-        } else if (scaleType == ImageView.ScaleType.FIT_XY) {
-            matrix = new Matrix();
-            float scaleX = imageView.getWidth();
-            scaleX /= drawableWidth;
-            float scaleY = imageView.getHeight();
-            scaleY /= drawableHeight;
-            matrix.setScale(scaleX, scaleY);
-        } else {
-            matrix = new Matrix(imageView.getImageMatrix());
-        }
-        return matrix;
-    }
-
-    private Rect findClip(ImageView imageView) {
-        if (imageView.getCropToPadding()) {
-            Rect clip = getClip(imageView);
-            clip.left += imageView.getPaddingLeft();
-            clip.right -= imageView.getPaddingRight();
-            clip.top += imageView.getPaddingTop();
-            clip.bottom -= imageView.getPaddingBottom();
-            return clip;
-        } else {
-            View view = imageView;
-            ViewParent viewParent;
-            while ((viewParent = view.getParent()) instanceof ViewGroup) {
-                ViewGroup viewGroup = (ViewGroup) viewParent;
-                if (viewGroup.getClipChildren()) {
-                    Rect clip = getClip(view);
-                    return clip;
-                }
-                view = viewGroup;
-            }
-        }
-        return null;
-    }
-
-    private Rect getClip(View clipView) {
-        Rect clipBounds = clipView.getClipBounds();
-        if (clipBounds == null) {
-            clipBounds = new Rect(clipView.getLeft(), clipView.getTop(),
-                    clipView.getRight(), clipView.getBottom());
-        }
-
-        ViewParent parent = clipView.getParent();
-        if (parent instanceof ViewGroup) {
-            ViewGroup parentViewGroup = (ViewGroup) parent;
-            parentViewGroup.getLocationInWindow(mTempLoc);
-            clipBounds.offset(mTempLoc[0], mTempLoc[1]);
-        }
-
-        return clipBounds;
-    }
-
-    @Override
-    public Transition clone() {
-        MoveImage clone = (MoveImage) super.clone();
-        clone.mTempLoc = new int[2];
-        return clone;
-    }
-
-    private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
-        static final Matrix sIdentity = new Matrix();
-        float[] mTempStartValues = new float[9];
-        float[] mTempEndValues = new float[9];
-        Matrix mTempMatrix = new Matrix();
-
-        @Override
-        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
-            if (startValue == null && endValue == null) {
-                return null;
-            }
-            if (startValue == null) {
-                startValue = sIdentity;
-            } else if (endValue == null) {
-                endValue = sIdentity;
-            }
-            startValue.getValues(mTempStartValues);
-            endValue.getValues(mTempEndValues);
-            for (int i = 0; i < 9; i++) {
-                float diff = mTempEndValues[i] - mTempStartValues[i];
-                mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
-            }
-            mTempMatrix.setValues(mTempEndValues);
-            return mTempMatrix;
-        }
+    public MoveImage() {
+        addTransition(new ChangeBounds());
+        addTransition(new ChangeClipBounds());
+        addTransition(new ChangeTransform());
+        addTransition(new ChangeImageTransform());
     }
 }
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index 8269258..0d2e487 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -16,14 +16,7 @@
 package android.transition;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.Property;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -41,71 +34,60 @@
  */
 public class Slide extends Visibility {
     private static final String TAG = "Slide";
-
     private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
     private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
-
+    private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
     private CalculateSlide mSlideCalculator = sCalculateBottom;
 
     private interface CalculateSlide {
-        /** Returns the translation value for view when it out of the scene */
-        float getGone(ViewGroup sceneRoot, View view);
 
-        /** Returns the translation value for view when it is in the scene */
-        float getHere(View view);
+        /** Returns the translation value for view when it goes out of the scene */
+        float getGoneX(ViewGroup sceneRoot, View view);
 
-        /** Returns the property to animate translation */
-        Property<View, Float> getProperty();
+        /** Returns the translation value for view when it goes out of the scene */
+        float getGoneY(ViewGroup sceneRoot, View view);
     }
 
     private static abstract class CalculateSlideHorizontal implements CalculateSlide {
-        @Override
-        public float getHere(View view) {
-            return view.getTranslationX();
-        }
 
         @Override
-        public Property<View, Float> getProperty() {
-            return View.TRANSLATION_X;
+        public float getGoneY(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY();
         }
     }
 
     private static abstract class CalculateSlideVertical implements CalculateSlide {
-        @Override
-        public float getHere(View view) {
-            return view.getTranslationY();
-        }
 
         @Override
-        public Property<View, Float> getProperty() {
-            return View.TRANSLATION_Y;
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX();
         }
     }
 
     private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
         @Override
-        public float getGone(ViewGroup sceneRoot, View view) {
+        public float getGoneX(ViewGroup sceneRoot, View view) {
             return view.getTranslationX() - sceneRoot.getWidth();
         }
     };
 
     private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
         @Override
-        public float getGone(ViewGroup sceneRoot, View view) {
+        public float getGoneY(ViewGroup sceneRoot, View view) {
             return view.getTranslationY() - sceneRoot.getHeight();
         }
     };
 
     private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
         @Override
-        public float getGone(ViewGroup sceneRoot, View view) {
+        public float getGoneX(ViewGroup sceneRoot, View view) {
             return view.getTranslationX() + sceneRoot.getWidth();
         }
     };
 
     private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
         @Override
-        public float getGone(ViewGroup sceneRoot, View view) {
+        public float getGoneY(ViewGroup sceneRoot, View view) {
             return view.getTranslationY() + sceneRoot.getHeight();
         }
     };
@@ -125,8 +107,28 @@
         setSlideEdge(slideEdge);
     }
 
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        int[] position = new int[2];
+        view.getLocationOnScreen(position);
+        transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
+    }
+
+    @Override
+    public void captureStartValues(TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
     /**
      * Change the edge that Views appear and disappear from.
+     *
      * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
      *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
      *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}.
@@ -153,77 +155,35 @@
         setPropagation(propagation);
     }
 
-    private Animator createAnimation(final View view, Property<View, Float> property,
-            float start, float end, float terminalValue, TimeInterpolator interpolator) {
-        view.setTranslationY(start);
-        if (start == end) {
-            return null;
-        }
-        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
-
-        SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end);
-        anim.addListener(listener);
-        anim.addPauseListener(listener);
-        anim.setInterpolator(interpolator);
-        return anim;
-    }
-
     @Override
     public Animator onAppear(ViewGroup sceneRoot, View view,
             TransitionValues startValues, TransitionValues endValues) {
         if (endValues == null) {
             return null;
         }
-        float end = mSlideCalculator.getHere(view);
-        float start = mSlideCalculator.getGone(sceneRoot, view);
-        return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate);
+        int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
+        float endX = view.getTranslationX();
+        float endY = view.getTranslationY();
+        float startX = mSlideCalculator.getGoneX(sceneRoot, view);
+        float startY = mSlideCalculator.getGoneY(sceneRoot, view);
+        return TranslationAnimationCreator
+                .createAnimation(view, endValues, position[0], position[1],
+                        startX, startY, endX, endY, sDecelerate);
     }
 
     @Override
     public Animator onDisappear(ViewGroup sceneRoot, View view,
             TransitionValues startValues, TransitionValues endValues) {
-        float start = mSlideCalculator.getHere(view);
-        float end = mSlideCalculator.getGone(sceneRoot, view);
-
-        return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
-                sAccelerate);
-    }
-
-    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
-        private boolean mCanceled = false;
-        private float mPausedY;
-        private final View mView;
-        private final float mEndY;
-        private final float mTerminalY;
-
-        public SlideAnimatorListener(View view, float terminalY, float endY) {
-            mView = view;
-            mTerminalY = terminalY;
-            mEndY = endY;
+        if (startValues == null) {
+            return null;
         }
-
-        @Override
-        public void onAnimationCancel(Animator animator) {
-            mView.setTranslationY(mTerminalY);
-            mCanceled = true;
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animator) {
-            if (!mCanceled) {
-                mView.setTranslationY(mTerminalY);
-            }
-        }
-
-        @Override
-        public void onAnimationPause(Animator animator) {
-            mPausedY = mView.getTranslationY();
-            mView.setTranslationY(mEndY);
-        }
-
-        @Override
-        public void onAnimationResume(Animator animator) {
-            mView.setTranslationY(mPausedY);
-        }
+        int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
+        float startX = view.getTranslationX();
+        float startY = view.getTranslationY();
+        float endX = mSlideCalculator.getGoneX(sceneRoot, view);
+        float endY = mSlideCalculator.getGoneY(sceneRoot, view);
+        return TranslationAnimationCreator
+                .createAnimation(view, startValues, position[0], position[1],
+                        startX, startY, endX, endY, sAccelerate);
     }
 }
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index e9c2bba..52e6691 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -68,11 +68,16 @@
  *
  * <p>This TransitionSet contains {@link android.transition.Explode} for visibility,
  * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
- * and {@link android.transition.ChangeClipBounds} for non-<code>ImageView</code>s and
- * {@link android.transition.MoveImage} for <code>ImageView</code>s:</p>
+ * and {@link android.transition.ChangeClipBounds} and
+ * {@link android.transition.ChangeImageTransform}:</p>
  *
  * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
  *
+ * <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
+ * <pre>&lt;transition class="my.app.transition.CustomTransition"/></pre>
+ * <p>Custom transition classes loaded from XML must have a public nullary (no argument)
+ * constructor.</p>
+ *
  * <p>Note that attributes for the transition are not required, just as they are
  * optional when declared in code; Transitions created from XML resources will use
  * the same defaults as their code-created equivalents. Here is a slightly more
@@ -89,8 +94,8 @@
  * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
  * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
  * of which lists a specific <code>targetId</code>, <code>targetClass</code>,
- * <code>targetViewName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
- * <code>excludeViewName</code>, which this transition acts upon.
+ * <code>targetName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
+ * <code>excludeName</code>, which this transition acts upon.
  * Use of targets is optional, but can be used to either limit the time spent checking
  * attributes on unchanging views, or limiting the types of animations run on specific views.
  * In this case, we know that only the <code>grayscaleContainer</code> will be
@@ -115,9 +120,9 @@
 
     /**
      * With {@link #setMatchOrder(int...)}, chooses to match by
-     * {@link android.view.View#getViewName()}. Null names will not be matched.
+     * {@link android.view.View#getTransitionName()}. Null names will not be matched.
      */
-    public static final int MATCH_VIEW_NAME = 0x2;
+    public static final int MATCH_NAME = 0x2;
 
     /**
      * With {@link #setMatchOrder(int...)}, chooses to match by
@@ -135,7 +140,7 @@
     private static final int MATCH_LAST = MATCH_ITEM_ID;
 
     private static final int[] DEFAULT_MATCH_ORDER = {
-        MATCH_VIEW_NAME,
+        MATCH_NAME,
         MATCH_INSTANCE,
         MATCH_ID,
         MATCH_ITEM_ID,
@@ -206,6 +211,10 @@
     // like CircularPropagation
     EpicenterCallback mEpicenterCallback;
 
+    // For Fragment shared element transitions, linking views explicitly by mismatching
+    // transitionNames.
+    ArrayMap<String, String> mNameOverrides;
+
     /**
      * Constructs a Transition object with no target objects. A transition with
      * no targets defaults to running on all target objects in the scene hierarchy
@@ -374,17 +383,17 @@
     /**
      * Sets the order in which Transition matches View start and end values.
      * <p>
-     * The default behavior is to match first by {@link android.view.View#getViewName()},
+     * The default behavior is to match first by {@link android.view.View#getTransitionName()},
      * then by View instance, then by {@link android.view.View#getId()} and finally
      * by its item ID if it is in a direct child of ListView. The caller can
      * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
-     * {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
+     * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
      * the match algorithms supplied will be used to determine whether Views are the
      * the same in both the start and end Scene. Views that do not match will be considered
      * as entering or leaving the Scene.
      * </p>
      * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
-     *                {@link #MATCH_VIEW_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
+     *                {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
      *                If none are provided, then the default match order will be set.
      */
     public void setMatchOrder(int... matches) {
@@ -496,9 +505,9 @@
     }
 
     /**
-     * Match start/end values by Adapter viewName. Adds matched values to startValuesList
+     * Match start/end values by Adapter transitionName. Adds matched values to startValuesList
      * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
-     * startNames and endNames as a guide for which Views have unique viewNames.
+     * startNames and endNames as a guide for which Views have unique transitionNames.
      */
     private void matchNames(ArrayList<TransitionValues> startValuesList,
             ArrayList<TransitionValues> endValuesList,
@@ -559,7 +568,7 @@
                 case MATCH_INSTANCE:
                     matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd);
                     break;
-                case MATCH_VIEW_NAME:
+                case MATCH_NAME:
                     matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
                             startValues.nameValues, endValues.nameValues);
                     break;
@@ -603,76 +612,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(), this,
+                                sceneRoot.getWindowId(), infoValues);
+                        runningAnimators.put(animator, info);
+                        mAnimators.add(animator);
                     }
                 }
             }
@@ -714,8 +723,8 @@
                 }
             }
         }
-        if (mTargetNameExcludes != null && target != null && target.getViewName() != null) {
-            if (mTargetNameExcludes.contains(target.getViewName())) {
+        if (mTargetNameExcludes != null && target != null && target.getTransitionName() != null) {
+            if (mTargetNameExcludes.contains(target.getTransitionName())) {
                 return false;
             }
         }
@@ -727,7 +736,7 @@
         if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
             return true;
         }
-        if (mTargetNames != null && mTargetNames.contains(target.getViewName())) {
+        if (mTargetNames != null && mTargetNames.contains(target.getTransitionName())) {
             return true;
         }
         if (mTargetTypes != null) {
@@ -878,18 +887,18 @@
     }
 
     /**
-     * Adds the viewName of a target view that this Transition is interested in
+     * Adds the transitionName of a target view that this Transition is interested in
      * animating. By default, there are no targetNames, and a Transition will
      * listen for changes on every view in the hierarchy below the sceneRoot
      * of the Scene being transitioned into. Setting targetNames constrains
-     * the Transition to only listen for, and act on, views with these viewNames.
-     * Views with different viewNames, or no viewName whatsoever, will be ignored.
+     * the Transition to only listen for, and act on, views with these transitionNames.
+     * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
      *
-     * <p>Note that viewNames should be unique within the view hierarchy.</p>
+     * <p>Note that transitionNames should be unique within the view hierarchy.</p>
      *
-     * @see android.view.View#getViewName()
-     * @param targetName The viewName of a target view, must be non-null.
-     * @return The Transition to which the target viewName is added.
+     * @see android.view.View#getTransitionName()
+     * @param targetName The transitionName of a target view, must be non-null.
+     * @return The Transition to which the target transitionName is added.
      * Returning the same object makes it easier to chain calls during
      * construction, such as
      * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
@@ -954,10 +963,10 @@
     }
 
     /**
-     * Removes the given targetName from the list of viewNames that this Transition
+     * Removes the given targetName from the list of transitionNames that this Transition
      * is interested in animating.
      *
-     * @param targetName The viewName of a target view, must not be null.
+     * @param targetName The transitionName of a target view, must not be null.
      * @return The Transition from which the targetName is removed.
      * Returning the same object makes it easier to chain calls during
      * construction, such as
@@ -999,28 +1008,28 @@
     }
 
     /**
-     * Whether to add the given viewName to the list of target viewNames to exclude from this
-     * transition. The <code>exclude</code> parameter specifies whether the target
+     * Whether to add the given transitionName to the list of target transitionNames to exclude
+     * from this transition. The <code>exclude</code> parameter specifies whether the target
      * should be added to or removed from the excluded list.
      *
      * <p>Excluding targets is a general mechanism for allowing transitions to run on
      * a view hierarchy while skipping target views that should not be part of
      * the transition. For example, you may want to avoid animating children
      * of a specific ListView or Spinner. Views can be excluded by their
-     * id, their instance reference, their viewName, or by the Class of that view
+     * id, their instance reference, their transitionName, or by the Class of that view
      * (eg, {@link Spinner}).</p>
      *
      * @see #excludeTarget(View, boolean)
      * @see #excludeTarget(int, boolean)
      * @see #excludeTarget(Class, boolean)
      *
-     * @param targetViewName The name of a target to ignore when running this transition.
+     * @param targetName The name of a target to ignore when running this transition.
      * @param exclude Whether to add the target to or remove the target from the
      * current list of excluded targets.
      * @return This transition object.
      */
-    public Transition excludeTarget(String targetViewName, boolean exclude) {
-        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetViewName, exclude);
+    public Transition excludeTarget(String targetName, boolean exclude) {
+        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
         return this;
     }
 
@@ -1244,7 +1253,7 @@
     /**
      * Returns the list of target IDs that this transition limits itself to
      * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
      * {@link #getTargetTypes()} then this transition is
      * not limited to specific views, and will handle changes to any views
      * in the hierarchy of a scene change.
@@ -1258,7 +1267,7 @@
     /**
      * Returns the list of target views that this transition limits itself to
      * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
      * {@link #getTargetTypes()} then this transition is
      * not limited to specific views, and will handle changes to any views
      * in the hierarchy of a scene change.
@@ -1270,23 +1279,31 @@
     }
 
     /**
-     * Returns the list of target viewNames that this transition limits itself to
+     * Returns the list of target transitionNames that this transition limits itself to
      * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
      * {@link #getTargetTypes()} then this transition is
      * not limited to specific views, and will handle changes to any views
      * in the hierarchy of a scene change.
      *
-     * @return the list of target viewNames
+     * @return the list of target transitionNames
+     */
+    public List<String> getTargetNames() {
+        return mTargetNames;
+    }
+
+    /**
+     * To be removed before L release.
+     * @hide
      */
     public List<String> getTargetViewNames() {
         return mTargetNames;
     }
 
     /**
-     * Returns the list of target viewNames that this transition limits itself to
+     * Returns the list of target transitionNames that this transition limits itself to
      * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetViewNames()}, and
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
      * {@link #getTargetTypes()} then this transition is
      * not limited to specific views, and will handle changes to any views
      * in the hierarchy of a scene change.
@@ -1347,6 +1364,21 @@
         } else {
             captureHierarchy(sceneRoot, start);
         }
+        if (!start && mNameOverrides != null) {
+            int numOverrides = mNameOverrides.size();
+            ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
+            for (int i = 0; i < numOverrides; i++) {
+                String fromName = mNameOverrides.keyAt(i);
+                overriddenViews.add(mStartValues.nameValues.remove(fromName));
+            }
+            for (int i = 0; i < numOverrides; i++) {
+                View view = overriddenViews.get(i);
+                if (view != null) {
+                    String toName = mNameOverrides.valueAt(i);
+                    mStartValues.nameValues.put(toName, view);
+                }
+            }
+        }
     }
 
     static void addViewValues(TransitionValuesMaps transitionValuesMaps,
@@ -1361,10 +1393,10 @@
                 transitionValuesMaps.idValues.put(id, view);
             }
         }
-        String name = view.getViewName();
+        String name = view.getTransitionName();
         if (name != null) {
             if (transitionValuesMaps.nameValues.containsKey(name)) {
-                // Duplicate viewNames: cannot match by viewName.
+                // Duplicate transitionNames: cannot match by transitionName.
                 transitionValuesMaps.nameValues.put(name, null);
             } else {
                 transitionValuesMaps.nameValues.put(name, view);
@@ -1400,10 +1432,12 @@
             mStartValues.viewValues.clear();
             mStartValues.idValues.clear();
             mStartValues.itemIdValues.clear();
+            mStartValues.nameValues.clear();
         } else {
             mEndValues.viewValues.clear();
             mEndValues.idValues.clear();
             mEndValues.itemIdValues.clear();
+            mEndValues.nameValues.clear();
         }
     }
 
@@ -1566,30 +1600,10 @@
                 AnimationInfo oldInfo = runningAnimators.get(anim);
                 if (oldInfo != null && oldInfo.view != null &&
                         oldInfo.view.getContext() == sceneRoot.getContext()) {
-                    boolean cancel = false;
                     TransitionValues oldValues = oldInfo.values;
                     View oldView = oldInfo.view;
                     TransitionValues newValues = mEndValues.viewValues.get(oldView);
-                    if (oldValues != null) {
-                        // if oldValues null, then transition didn't care to stash values,
-                        // and won't get canceled
-                        if (newValues != null) {
-                            for (String key : oldValues.values.keySet()) {
-                                Object oldValue = oldValues.values.get(key);
-                                Object newValue = newValues.values.get(key);
-                                if (oldValue != null && newValue != null &&
-                                        !oldValue.equals(newValue)) {
-                                    cancel = true;
-                                    if (DBG) {
-                                        Log.d(LOG_TAG, "Transition.playTransition: " +
-                                                "oldValue != newValue for " + key +
-                                                ": old, new = " + oldValue + ", " + newValue);
-                                    }
-                                    break;
-                                }
-                            }
-                        }
-                    }
+                    boolean cancel = oldInfo.transition.areValuesChanged(oldValues, newValues);
                     if (cancel) {
                         if (anim.isRunning() || anim.isStarted()) {
                             if (DBG) {
@@ -1611,6 +1625,29 @@
         runAnimators();
     }
 
+    boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
+        boolean valuesChanged = false;
+        // if oldValues null, then transition didn't care to stash values,
+        // and won't get canceled
+        if (oldValues != null && newValues != null) {
+            for (String key : oldValues.values.keySet()) {
+                Object oldValue = oldValues.values.get(key);
+                Object newValue = newValues.values.get(key);
+                if (oldValue != null && newValue != null &&
+                        !oldValue.equals(newValue)) {
+                    valuesChanged = true;
+                    if (DBG) {
+                        Log.d(LOG_TAG, "Transition.playTransition: " +
+                                "oldValue != newValue for " + key +
+                                ": old, new = " + oldValue + ", " + newValue);
+                    }
+                    break;
+                }
+            }
+        }
+        return valuesChanged;
+    }
+
     /**
      * This is a utility method used by subclasses to handle standard parts of
      * setting up and running an Animator: it sets the {@link #getDuration()
@@ -1866,6 +1903,20 @@
         return mCanRemoveViews;
     }
 
+    /**
+     * Sets the shared element names -- a mapping from a name at the start state to
+     * a different name at the end state.
+     * @hide
+     */
+    public void setNameOverrides(ArrayMap<String, String> overrides) {
+        mNameOverrides = overrides;
+    }
+
+    /** @hide */
+    public ArrayMap<String, String> getNameOverrides() {
+        return mNameOverrides;
+    }
+
     @Override
     public String toString() {
         return toString("");
@@ -2035,12 +2086,15 @@
         String name;
         TransitionValues values;
         WindowId windowId;
+        Transition transition;
 
-        AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) {
+        AnimationInfo(View view, String name, Transition transition,
+                WindowId windowId, TransitionValues values) {
             this.view = view;
             this.name = name;
             this.values = values;
             this.windowId = windowId;
+            this.transition = transition;
         }
     }
 
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 5b7c737..e0c3cae 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -30,7 +30,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.StringTokenizer;
 
 /**
@@ -43,6 +42,8 @@
  */
 public class TransitionInflater {
     private static final String MATCH_INSTANCE = "instance";
+    private static final String MATCH_NAME = "name";
+    /** To be removed before L release */
     private static final String MATCH_VIEW_NAME = "viewName";
     private static final String MATCH_ID = "id";
     private static final String MATCH_ITEM_ID = "itemId";
@@ -90,8 +91,6 @@
     /**
      * Loads a {@link TransitionManager} object from a resource
      *
-     *
-     *
      * @param resource The resource id of the transition manager to load
      * @return The loaded TransitionManager object
      * @throws android.content.res.Resources.NotFoundException when the
@@ -159,6 +158,9 @@
             } else if ("moveImage".equals(name)) {
                 transition = new MoveImage();
                 newTransition = true;
+            } else if ("changeImageTransform".equals(name)) {
+                transition = new ChangeImageTransform();
+                newTransition = true;
             } else if ("changeTransform".equals(name)) {
                 transition = new ChangeTransform();
                 newTransition = true;
@@ -182,6 +184,8 @@
                 createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
                 a.recycle();
                 newTransition = true;
+            } else if ("transition".equals(name)) {
+                transition = createCustomTransition(attrs);
             } else if ("targets".equals(name)) {
                 if (parser.getDepth() - 1 > depth && transition != null) {
                     // We're inside the child tag - add targets to the child
@@ -206,6 +210,29 @@
         return transition;
     }
 
+    private Transition createCustomTransition(AttributeSet attrs) {
+        String className = attrs.getAttributeValue(null, "class");
+
+        if (className == null) {
+            throw new RuntimeException("transition tag must have a 'class' attribute");
+        }
+
+        try {
+            Class c = Class.forName(className);
+            if (!Transition.class.isAssignableFrom(c)) {
+                throw new RuntimeException("transition class must be a Transition: " + className);
+            }
+            return (Transition) c.newInstance();
+        } catch (InstantiationException e) {
+            throw new RuntimeException("Could not instantiate transition class", e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Could not access default constructor for transition class "
+                    + className, e);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("Could not find transition class " + className, e);
+        }
+    }
+
     private Slide createSlideTransition(AttributeSet attrs) {
         TypedArray a = mContext.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.Slide);
@@ -235,20 +262,20 @@
                         com.android.internal.R.styleable.TransitionTarget);
                 int id = a.getResourceId(
                         com.android.internal.R.styleable.TransitionTarget_targetId, -1);
-                String viewName;
+                String transitionName;
                 if (id >= 0) {
                     transition.addTarget(id);
                 } else if ((id = a.getResourceId(
                         com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
                     transition.excludeTarget(id, true);
-                } else if ((viewName = a.getString(
-                            com.android.internal.R.styleable.TransitionTarget_targetViewName))
+                } else if ((transitionName = a.getString(
+                            com.android.internal.R.styleable.TransitionTarget_targetName))
                         != null) {
-                    transition.addTarget(viewName);
-                } else if ((viewName = a.getString(
-                        com.android.internal.R.styleable.TransitionTarget_excludeViewName))
+                    transition.addTarget(transitionName);
+                } else if ((transitionName = a.getString(
+                        com.android.internal.R.styleable.TransitionTarget_excludeName))
                         != null) {
-                    transition.excludeTarget(viewName, true);
+                    transition.excludeTarget(transitionName, true);
                 } else {
                     String className = a.getString(
                             com.android.internal.R.styleable.TransitionTarget_excludeClass);
@@ -282,8 +309,10 @@
                 matches[index] = Transition.MATCH_ID;
             } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) {
                 matches[index] = Transition.MATCH_INSTANCE;
+            } else if (MATCH_NAME.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_NAME;
             } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) {
-                matches[index] = Transition.MATCH_VIEW_NAME;
+                matches[index] = Transition.MATCH_NAME;
             } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) {
                 matches[index] = Transition.MATCH_ITEM_ID;
             } else if (token.isEmpty()) {
@@ -323,6 +352,16 @@
             transition.setMatchOrder(parseMatchOrder(matchOrder));
         }
         a.recycle();
+        if (transition instanceof Visibility) {
+            a = mContext.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.VisibilityTransition);
+            int mode = a.getInt(
+                    com.android.internal.R.styleable.VisibilityTransition_visibilityMode, 0);
+            a.recycle();
+            if (mode != 0) {
+                ((Visibility)transition).setMode(mode);
+            }
+        }
         return transition;
     }
 
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 698b563..495814a 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -168,30 +168,106 @@
 
     @Override
     public TransitionSet addTarget(View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(target);
+        }
         return (TransitionSet) super.addTarget(target);
     }
 
     @Override
     public TransitionSet addTarget(int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetId);
+        }
         return (TransitionSet) super.addTarget(targetId);
     }
 
     @Override
+    public TransitionSet addTarget(String targetName) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetName);
+        }
+        return (TransitionSet) super.addTarget(targetName);
+    }
+
+    @Override
+    public TransitionSet addTarget(Class targetType) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetType);
+        }
+        return (TransitionSet) super.addTarget(targetType);
+    }
+
+    @Override
     public TransitionSet addListener(TransitionListener listener) {
         return (TransitionSet) super.addListener(listener);
     }
 
     @Override
     public TransitionSet removeTarget(int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(targetId);
+        }
         return (TransitionSet) super.removeTarget(targetId);
     }
 
     @Override
     public TransitionSet removeTarget(View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
         return (TransitionSet) super.removeTarget(target);
     }
 
     @Override
+    public TransitionSet removeTarget(Class target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @Override
+    public TransitionSet removeTarget(String target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @Override
+    public Transition excludeTarget(View target, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(target, exclude);
+        }
+        return super.excludeTarget(target, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(String targetName, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetName, exclude);
+        }
+        return super.excludeTarget(targetName, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(int targetId, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetId, exclude);
+        }
+        return super.excludeTarget(targetId, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(Class type, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(type, exclude);
+        }
+        return super.excludeTarget(type, exclude);
+    }
+
+    @Override
     public TransitionSet removeListener(TransitionListener listener) {
         return (TransitionSet) super.removeListener(listener);
     }
diff --git a/core/java/android/transition/TranslationAnimationCreator.java b/core/java/android/transition/TranslationAnimationCreator.java
new file mode 100644
index 0000000..de71fd7
--- /dev/null
+++ b/core/java/android/transition/TranslationAnimationCreator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.transition;
+
+import com.android.internal.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Path;
+import android.view.View;
+
+/**
+ * This class is used by Slide and Explode to create an animator that goes from the start
+ * position to the end position. It takes into account the canceled position so that it
+ * will not blink out or shift suddenly when the transition is interrupted.
+ */
+class TranslationAnimationCreator {
+
+    /**
+     * Creates an animator that can be used for x and/or y translations. When interrupted,
+     * it sets a tag to keep track of the position so that it may be continued from position.
+     *
+     * @param view The view being moved. This may be in the overlay for onDisappear.
+     * @param values The values containing the view in the view hierarchy.
+     * @param viewPosX The x screen coordinate of view
+     * @param viewPosY The y screen coordinate of view
+     * @param startX The start translation x of view
+     * @param startY The start translation y of view
+     * @param endX The end translation x of view
+     * @param endY The end translation y of view
+     * @param interpolator The interpolator to use with this animator.
+     * @return An animator that moves from (startX, startY) to (endX, endY) unless there was
+     * a previous interruption, in which case it moves from the current position to (endX, endY).
+     */
+    static Animator createAnimation(View view, TransitionValues values, int viewPosX, int viewPosY,
+            float startX, float startY, float endX, float endY, TimeInterpolator interpolator) {
+        float terminalX = view.getTranslationX();
+        float terminalY = view.getTranslationY();
+        int[] startPosition = (int[]) values.view.getTag(R.id.transitionPosition);
+        if (startPosition != null) {
+            startX = startPosition[0] - viewPosX + terminalX;
+            startY = startPosition[1] - viewPosY + terminalY;
+        }
+        // Initial position is at translation startX, startY, so position is offset by that amount
+        int startPosX = viewPosX + Math.round(startX - terminalX);
+        int startPosY = viewPosY + Math.round(startY - terminalY);
+
+        view.setTranslationX(startX);
+        view.setTranslationY(startY);
+        if (startX == endX && startY == endY) {
+            return null;
+        }
+        Path path = new Path();
+        path.moveTo(startX, startY);
+        path.lineTo(endX, endY);
+        ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y,
+                path);
+
+        TransitionPositionListener listener = new TransitionPositionListener(view, values.view,
+                startPosX, startPosY, terminalX, terminalY);
+        anim.addListener(listener);
+        anim.addPauseListener(listener);
+        anim.setInterpolator(interpolator);
+        return anim;
+    }
+
+    private static class TransitionPositionListener extends AnimatorListenerAdapter {
+
+        private final View mViewInHierarchy;
+        private final View mMovingView;
+        private final int mStartX;
+        private final int mStartY;
+        private int[] mTransitionPosition;
+        private float mPausedX;
+        private float mPausedY;
+        private final float mTerminalX;
+        private final float mTerminalY;
+
+        private TransitionPositionListener(View movingView, View viewInHierarchy,
+                int startX, int startY, float terminalX, float terminalY) {
+            mMovingView = movingView;
+            mViewInHierarchy = viewInHierarchy;
+            mStartX = startX - Math.round(mMovingView.getTranslationX());
+            mStartY = startY - Math.round(mMovingView.getTranslationY());
+            mTerminalX = terminalX;
+            mTerminalY = terminalY;
+            mTransitionPosition = (int[]) mViewInHierarchy.getTag(R.id.transitionPosition);
+            if (mTransitionPosition != null) {
+                mViewInHierarchy.setTagInternal(R.id.transitionPosition, null);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mTransitionPosition == null) {
+                mTransitionPosition = new int[2];
+            }
+            mTransitionPosition[0] = Math.round(mStartX + mMovingView.getTranslationX());
+            mTransitionPosition[1] = Math.round(mStartY + mMovingView.getTranslationY());
+            mViewInHierarchy.setTagInternal(R.id.transitionPosition, mTransitionPosition);
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedX = mMovingView.getTranslationX();
+            mPausedY = mMovingView.getTranslationY();
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mMovingView.setTranslationX(mPausedX);
+            mMovingView.setTranslationY(mPausedY);
+        }
+    }
+
+}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 0f7638b..aa9f04e 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -18,6 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -40,6 +43,20 @@
     private static final String PROPNAME_PARENT = "android:visibility:parent";
     private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
 
+    /**
+     * Mode used in {@link #setMode(int)} to make the transition
+     * operate on targets that are appearing. Maybe be combined with
+     * {@link #OUT} to target Visibility changes both in and out.
+     */
+    public static final int IN = 0x1;
+
+    /**
+     * Mode used in {@link #setMode(int)} to make the transition
+     * operate on targets that are disappearing. Maybe be combined with
+     * {@link #IN} to target Visibility changes both in and out.
+     */
+    public static final int OUT = 0x2;
+
     private static final String[] sTransitionProperties = {
             PROPNAME_VISIBILITY,
             PROPNAME_PARENT,
@@ -55,6 +72,22 @@
         ViewGroup endParent;
     }
 
+    private int mMode = IN | OUT;
+
+    /**
+     * Changes the transition to support appearing and/or disappearing Views, depending
+     * on <code>mode</code>.
+     *
+     * @param mode The behavior supported by this transition, a combination of
+     *             {@link #IN} and {@link #OUT}.
+     */
+    public void setMode(int mode) {
+        if ((mode & ~(IN | OUT)) != 0) {
+            throw new IllegalArgumentException("Only IN and OUT flags are allowed");
+        }
+        mMode = mode;
+    }
+
     @Override
     public String[] getTransitionProperties() {
         return sTransitionProperties;
@@ -197,6 +230,9 @@
     public Animator onAppear(ViewGroup sceneRoot,
             TransitionValues startValues, int startVisibility,
             TransitionValues endValues, int endVisibility) {
+        if ((mMode & IN) != IN || endValues == null) {
+            return null;
+        }
         return onAppear(sceneRoot, endValues.view, startValues, endValues);
     }
 
@@ -257,6 +293,10 @@
     public Animator onDisappear(ViewGroup sceneRoot,
             TransitionValues startValues, int startVisibility,
             TransitionValues endValues, int endVisibility) {
+        if ((mMode & OUT) != OUT) {
+            return null;
+        }
+
         View startView = (startValues != null) ? startValues.view : null;
         View endView = (endValues != null) ? endValues.view : null;
         View overlayView = null;
@@ -272,15 +312,23 @@
                 if (startView.getParent() == null) {
                     // no parent - safe to use
                     overlayView = startView;
-                } else if (startView.getParent() instanceof View &&
-                        startView.getParent().getParent() == null) {
+                } else if (startView.getParent() instanceof View) {
                     View startParent = (View) startView.getParent();
-                    int id = startParent.getId();
-                    if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
-                        // no parent, but its parent is unparented  but the parent
-                        // hierarchy has been replaced by a new hierarchy with the same id
-                        // and it is safe to un-parent startView
-                        overlayView = startView;
+                    if (!isValidTarget(startParent)) {
+                        if (startView.isAttachedToWindow()) {
+                            overlayView = copyViewImage(startView);
+                        } else {
+                            overlayView = startView;
+                        }
+                    } else if (startParent.getParent() == null) {
+                        int id = startParent.getId();
+                        if (id != View.NO_ID && sceneRoot.findViewById(id) != null
+                                && mCanRemoveViews) {
+                            // no parent, but its parent is unparented  but the parent
+                            // hierarchy has been replaced by a new hierarchy with the same id
+                            // and it is safe to un-parent startView
+                            overlayView = startView;
+                        }
                     }
                 }
             }
@@ -320,16 +368,6 @@
                     public void onAnimationEnd(Animator animation) {
                         finalSceneRoot.getOverlay().remove(finalOverlayView);
                     }
-
-                    @Override
-                    public void onAnimationPause(Animator animation) {
-                        finalSceneRoot.getOverlay().remove(finalOverlayView);
-                    }
-
-                    @Override
-                    public void onAnimationResume(Animator animation) {
-                        finalSceneRoot.getOverlay().add(finalOverlayView);
-                    }
                 });
             }
             return animator;
@@ -378,6 +416,36 @@
         return null;
     }
 
+    private View copyViewImage(View view) {
+        int width = view.getWidth();
+        int height = view.getHeight();
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        view.draw(canvas);
+        final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+
+        View overlayView = new View(view.getContext());
+        overlayView.setBackground(drawable);
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+        overlayView.measure(widthSpec, heightSpec);
+        overlayView.layout(0, 0, width, height);
+        return overlayView;
+    }
+
+    @Override
+    boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
+        VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues);
+        if (oldValues == null && newValues == null) {
+            return false;
+        }
+        return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE ||
+            changeInfo.endVisibility == View.VISIBLE);
+    }
+
     /**
      * The default implementation of this method returns a null Animator. Subclasses should
      * override this method to make targets disappear with the desired transition. The
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
new file mode 100644
index 0000000..f4a0448
--- /dev/null
+++ b/core/java/android/util/PathParser.java
@@ -0,0 +1,609 @@
+/*
+ * 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.util;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class PathParser {
+    static final String LOGTAG = PathParser.class.getSimpleName();
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return the generated Path object.
+     */
+    public static Path createPathFromPathData(String pathData) {
+        Path path = new Path();
+        PathDataNode[] nodes = createNodesFromPathData(pathData);
+        if (nodes != null) {
+            PathDataNode.nodesToPath(nodes, path);
+            return path;
+        }
+        return null;
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return an array of the PathDataNode.
+     */
+    public static PathDataNode[] createNodesFromPathData(String pathData) {
+        if (pathData == null) {
+            return null;
+        }
+        int start = 0;
+        int end = 1;
+
+        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+        while (end < pathData.length()) {
+            end = nextStart(pathData, end);
+            String s = pathData.substring(start, end);
+            float[] val = getFloats(s);
+            addNode(list, s.charAt(0), val);
+
+            start = end;
+            end++;
+        }
+        if ((end - start) == 1 && start < pathData.length()) {
+            addNode(list, pathData.charAt(start), new float[0]);
+        }
+        return list.toArray(new PathDataNode[list.size()]);
+    }
+
+    /**
+     * @param source The array of PathDataNode to be duplicated.
+     * @return a deep copy of the <code>source</code>.
+     */
+    public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
+        PathDataNode[] copy = new PathParser.PathDataNode[source.length];
+        for (int i = 0; i < source.length; i ++) {
+            copy[i] = new PathDataNode(source[i]);
+        }
+        return copy;
+    }
+
+    /**
+     * @param nodesFrom The source path represented in an array of PathDataNode
+     * @param nodesTo The target path represented in an array of PathDataNode
+     * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+     */
+    public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
+        if (nodesFrom == null || nodesTo == null) {
+            return false;
+        }
+
+        if (nodesFrom.length != nodesTo.length) {
+            return false;
+        }
+
+        for (int i = 0; i < nodesFrom.length; i ++) {
+            if (nodesFrom[i].mType != nodesTo[i].mType
+                    || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Update the target's data to match the source.
+     * Before calling this, make sure canMorph(target, source) is true.
+     *
+     * @param target The target path represented in an array of PathDataNode
+     * @param source The source path represented in an array of PathDataNode
+     */
+    public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
+        for (int i = 0; i < source.length; i ++) {
+            target[i].mType = source[i].mType;
+            for (int j = 0; j < source[i].mParams.length; j ++) {
+                target[i].mParams[j] = source[i].mParams[j];
+            }
+        }
+    }
+
+    private static int nextStart(String s, int end) {
+        char c;
+
+        while (end < s.length()) {
+            c = s.charAt(end);
+            if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
+                return end;
+            }
+            end++;
+        }
+        return end;
+    }
+
+    private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
+        list.add(new PathDataNode(cmd, val));
+    }
+
+
+    /**
+     * Parse the floats in the string.
+     * This is an optimized version of parseFloat(s.split(",|\\s"));
+     *
+     * @param s the string containing a command and list of floats
+     * @return array of floats
+     */
+    private static float[] getFloats(String s) {
+        if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
+            return new float[0];
+        }
+        try {
+            float[] tmp = new float[s.length()];
+            int count = 0;
+            int pos = 1, end;
+            while ((end = extract(s, pos)) >= 0) {
+                if (pos < end) {
+                    tmp[count++] = Float.parseFloat(s.substring(pos, end));
+                }
+                pos = end + 1;
+            }
+            // handle the final float if there is one
+            if (pos < s.length()) {
+                tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
+            }
+            return Arrays.copyOf(tmp, count);
+        } catch (NumberFormatException e){
+            Log.e(LOGTAG,"error in parsing \""+s+"\"");
+            throw e;
+        }
+    }
+
+    /**
+     * Calculate the position of the next comma or space
+     * @param s the string to search
+     * @param start the position to start searching
+     * @return the position of the next comma or space or -1 if none found
+     */
+    private static int extract(String s, int start) {
+        int space = s.indexOf(' ', start);
+        int comma = s.indexOf(',', start);
+        if (space == -1) {
+            return comma;
+        }
+        if (comma == -1) {
+            return space;
+        }
+        return (comma > space) ? space : comma;
+    }
+
+    /**
+     * Each PathDataNode represents one command in the "d" attribute of the svg
+     * file.
+     * An array of PathDataNode can represent the whole "d" attribute.
+     */
+    public static class PathDataNode {
+        private char mType;
+        private float[] mParams;
+
+        private PathDataNode(char type, float[] params) {
+            mType = type;
+            mParams = params;
+        }
+
+        private PathDataNode(PathDataNode n) {
+            mType = n.mType;
+            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+        }
+
+        /**
+         * Convert an array of PathDataNode to Path.
+         *
+         * @param node The source array of PathDataNode.
+         * @param path The target Path object.
+         */
+        public static void nodesToPath(PathDataNode[] node, Path path) {
+            float[] current = new float[4];
+            char previousCommand = 'm';
+            for (int i = 0; i < node.length; i++) {
+                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+                previousCommand = node[i].mType;
+            }
+        }
+
+        /**
+         * The current PathDataNode will be interpolated between the
+         * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+         * <code>fraction</code>.
+         *
+         * @param nodeFrom The start value as a PathDataNode.
+         * @param nodeTo The end value as a PathDataNode
+         * @param fraction The fraction to interpolate.
+         */
+        public void interpolatePathDataNode(PathDataNode nodeFrom,
+                PathDataNode nodeTo, float fraction) {
+            for (int i = 0; i < nodeFrom.mParams.length; i++) {
+                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+                        + nodeTo.mParams[i] * fraction;
+            }
+        }
+
+        private static void addCommand(Path path, float[] current,
+                char previousCmd, char cmd, float[] val) {
+
+            int incr = 2;
+            float currentX = current[0];
+            float currentY = current[1];
+            float ctrlPointX = current[2];
+            float ctrlPointY = current[3];
+            float reflectiveCtrlPointX;
+            float reflectiveCtrlPointY;
+
+            switch (cmd) {
+                case 'z':
+                case 'Z':
+                    path.close();
+                    return;
+                case 'm':
+                case 'M':
+                case 'l':
+                case 'L':
+                case 't':
+                case 'T':
+                    incr = 2;
+                    break;
+                case 'h':
+                case 'H':
+                case 'v':
+                case 'V':
+                    incr = 1;
+                    break;
+                case 'c':
+                case 'C':
+                    incr = 6;
+                    break;
+                case 's':
+                case 'S':
+                case 'q':
+                case 'Q':
+                    incr = 4;
+                    break;
+                case 'a':
+                case 'A':
+                    incr = 7;
+                    break;
+            }
+            for (int k = 0; k < val.length; k += incr) {
+                switch (cmd) {
+                    case 'm': // moveto - Start a new sub-path (relative)
+                        path.rMoveTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'M': // moveto - Start a new sub-path
+                        path.moveTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'l': // lineto - Draw a line from the current point (relative)
+                        path.rLineTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'L': // lineto - Draw a line from the current point
+                        path.lineTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'z': // closepath - Close the current subpath
+                    case 'Z': // closepath - Close the current subpath
+                        path.close();
+                        break;
+                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
+                        path.rLineTo(val[k + 0], 0);
+                        currentX += val[k + 0];
+                        break;
+                    case 'H': // horizontal lineto - Draws a horizontal line
+                        path.lineTo(val[k + 0], currentY);
+                        currentX = val[k + 0];
+                        break;
+                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+                        path.rLineTo(0, val[k + 0]);
+                        currentY += val[k + 0];
+                        break;
+                    case 'V': // vertical lineto - Draws a vertical line from the current point
+                        path.lineTo(currentX, val[k + 0]);
+                        currentY = val[k + 0];
+                        break;
+                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
+                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+
+                        ctrlPointX = currentX + val[k + 2];
+                        ctrlPointY = currentY + val[k + 3];
+                        currentX += val[k + 4];
+                        currentY += val[k + 5];
+
+                        break;
+                    case 'C': // curveto - Draws a cubic Bézier curve
+                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+                        currentX = val[k + 4];
+                        currentY = val[k + 5];
+                        ctrlPointX = val[k + 2];
+                        ctrlPointY = val[k + 3];
+                        break;
+                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1],
+                                val[k + 2], val[k + 3]);
+
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 'q': // Draws a quadratic Bézier (relative)
+                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'Q': // Draws a quadratic Bézier
+                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = currentX + reflectiveCtrlPointX;
+                        ctrlPointY = currentY + reflectiveCtrlPointY;
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = reflectiveCtrlPointX;
+                        ctrlPointY = reflectiveCtrlPointY;
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'a': // Draws an elliptical arc
+                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5] + currentX,
+                                val[k + 6] + currentY,
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX += val[k + 5];
+                        currentY += val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                    case 'A': // Draws an elliptical arc
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5],
+                                val[k + 6],
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX = val[k + 5];
+                        currentY = val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                }
+                previousCmd = cmd;
+            }
+            current[0] = currentX;
+            current[1] = currentY;
+            current[2] = ctrlPointX;
+            current[3] = ctrlPointY;
+        }
+
+        private static void drawArc(Path p,
+                float x0,
+                float y0,
+                float x1,
+                float y1,
+                float a,
+                float b,
+                float theta,
+                boolean isMoreThanHalf,
+                boolean isPositiveArc) {
+
+            /* Convert rotation angle from degrees to radians */
+            double thetaD = Math.toRadians(theta);
+            /* Pre-compute rotation matrix entries */
+            double cosTheta = Math.cos(thetaD);
+            double sinTheta = Math.sin(thetaD);
+            /* Transform (x0, y0) and (x1, y1) into unit space */
+            /* using (inverse) rotation, followed by (inverse) scale */
+            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+            /* Compute differences and averages */
+            double dx = x0p - x1p;
+            double dy = y0p - y1p;
+            double xm = (x0p + x1p) / 2;
+            double ym = (y0p + y1p) / 2;
+            /* Solve for intersecting unit circles */
+            double dsq = dx * dx + dy * dy;
+            if (dsq == 0.0) {
+                Log.w(LOGTAG, " Points are coincident");
+                return; /* Points are coincident */
+            }
+            double disc = 1.0 / dsq - 1.0 / 4.0;
+            if (disc < 0.0) {
+                Log.w(LOGTAG, "Points are too far apart " + dsq);
+                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+                drawArc(p, x0, y0, x1, y1, a * adjust,
+                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
+                return; /* Points are too far apart */
+            }
+            double s = Math.sqrt(disc);
+            double sdx = s * dx;
+            double sdy = s * dy;
+            double cx;
+            double cy;
+            if (isMoreThanHalf == isPositiveArc) {
+                cx = xm - sdy;
+                cy = ym + sdx;
+            } else {
+                cx = xm + sdy;
+                cy = ym - sdx;
+            }
+
+            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+
+            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+
+            double sweep = (eta1 - eta0);
+            if (isPositiveArc != (sweep >= 0)) {
+                if (sweep > 0) {
+                    sweep -= 2 * Math.PI;
+                } else {
+                    sweep += 2 * Math.PI;
+                }
+            }
+
+            cx *= a;
+            cy *= b;
+            double tcx = cx;
+            cx = cx * cosTheta - cy * sinTheta;
+            cy = tcx * sinTheta + cy * cosTheta;
+
+            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+        }
+
+        /**
+         * Converts an arc to cubic Bezier segments and records them in p.
+         *
+         * @param p The target for the cubic Bezier segments
+         * @param cx The x coordinate center of the ellipse
+         * @param cy The y coordinate center of the ellipse
+         * @param a The radius of the ellipse in the horizontal direction
+         * @param b The radius of the ellipse in the vertical direction
+         * @param e1x E(eta1) x coordinate of the starting point of the arc
+         * @param e1y E(eta2) y coordinate of the starting point of the arc
+         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+         * @param start The start angle of the arc on the ellipse
+         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+         */
+        private static void arcToBezier(Path p,
+                double cx,
+                double cy,
+                double a,
+                double b,
+                double e1x,
+                double e1y,
+                double theta,
+                double start,
+                double sweep) {
+            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+            // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+            // Maximum of 45 degrees per cubic Bezier segment
+            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+
+            double eta1 = start;
+            double cosTheta = Math.cos(theta);
+            double sinTheta = Math.sin(theta);
+            double cosEta1 = Math.cos(eta1);
+            double sinEta1 = Math.sin(eta1);
+            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+            double anglePerSegment = sweep / numSegments;
+            for (int i = 0; i < numSegments; i++) {
+                double eta2 = eta1 + anglePerSegment;
+                double sinEta2 = Math.sin(eta2);
+                double cosEta2 = Math.cos(eta2);
+                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+                double alpha =
+                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+                double q1x = e1x + alpha * ep1x;
+                double q1y = e1y + alpha * ep1y;
+                double q2x = e2x - alpha * ep2x;
+                double q2y = e2y - alpha * ep2y;
+
+                p.cubicTo((float) q1x,
+                        (float) q1y,
+                        (float) q2x,
+                        (float) q2y,
+                        (float) e2x,
+                        (float) e2y);
+                eta1 = eta2;
+                e1x = e2x;
+                e1y = e2y;
+                ep1x = ep2x;
+                ep1y = ep2y;
+            }
+        }
+    }
+}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 8f4b710..f7d2821 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -62,10 +62,7 @@
      */
     public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
         TimeZone best = null;
-
-        Resources r = Resources.getSystem();
-        XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
-        Date d = new Date(when);
+        final Date d = new Date(when);
 
         TimeZone current = TimeZone.getDefault();
         String currentName = current.getID();
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index d7a913d..76a6f52 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -599,6 +599,40 @@
     }
 
     /**
+     * Gets the app VSYNC offset, in nanoseconds.  This is a positive value indicating
+     * the phase offset of the VSYNC events provided by Choreographer relative to the
+     * display refresh.  For example, if Choreographer reports that the refresh occurred
+     * at time N, it actually occurred at (N - appVsyncOffset).
+     * <p>
+     * Apps generally do not need to be aware of this.  It's only useful for fine-grained
+     * A/V synchronization.
+     */
+    public long getAppVsyncOffsetNanos() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.appVsyncOffsetNanos;
+        }
+    }
+
+    /**
+     * This is how far in advance a buffer must be queued for presentation at
+     * a given time.  If you want a buffer to appear on the screen at
+     * time N, you must submit the buffer before (N - presentationDeadline).
+     * <p>
+     * The desired presentation time for GLES rendering may be set with
+     * {@link android.opengl.EGLExt#eglPresentationTimeANDROID}.  For video decoding, use
+     * {@link android.media.MediaCodec#releaseOutputBuffer(int, long)}.  Times are
+     * expressed in nanoseconds, using the system monotonic clock
+     * ({@link System#nanoTime}).
+     */
+    public long getPresentationDeadlineNanos() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.presentationDeadlineNanos;
+        }
+    }
+
+    /**
      * Gets display metrics that describe the size and density of this display.
      * <p>
      * The size is adjusted based on the current rotation of the display.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index b0fe0fa..98696c7 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,20 @@
     public float physicalYDpi;
 
     /**
+     * This is a positive value indicating the phase offset of the VSYNC events provided by
+     * Choreographer relative to the display refresh.  For example, if Choreographer reports
+     * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos).
+     */
+    public long appVsyncOffsetNanos;
+
+    /**
+     * This is how far in advance a buffer must be queued for presentation at
+     * a given time.  If you want a buffer to appear on the screen at
+     * time N, you must submit the buffer before (N - bufferDeadlineNanos).
+     */
+    public long presentationDeadlineNanos;
+
+    /**
      * The state of the display, such as {@link android.view.Display#STATE_ON}.
      */
     public int state;
@@ -253,6 +267,8 @@
                 && logicalDensityDpi == other.logicalDensityDpi
                 && physicalXDpi == other.physicalXDpi
                 && physicalYDpi == other.physicalYDpi
+                && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+                && presentationDeadlineNanos == other.presentationDeadlineNanos
                 && state == other.state
                 && ownerUid == other.ownerUid
                 && Objects.equal(ownerPackageName, other.ownerPackageName);
@@ -286,6 +302,8 @@
         logicalDensityDpi = other.logicalDensityDpi;
         physicalXDpi = other.physicalXDpi;
         physicalYDpi = other.physicalYDpi;
+        appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+        presentationDeadlineNanos = other.presentationDeadlineNanos;
         state = other.state;
         ownerUid = other.ownerUid;
         ownerPackageName = other.ownerPackageName;
@@ -314,6 +332,8 @@
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
         physicalYDpi = source.readFloat();
+        appVsyncOffsetNanos = source.readLong();
+        presentationDeadlineNanos = source.readLong();
         state = source.readInt();
         ownerUid = source.readInt();
         ownerPackageName = source.readString();
@@ -343,6 +363,8 @@
         dest.writeInt(logicalDensityDpi);
         dest.writeFloat(physicalXDpi);
         dest.writeFloat(physicalYDpi);
+        dest.writeLong(appVsyncOffsetNanos);
+        dest.writeLong(presentationDeadlineNanos);
         dest.writeInt(state);
         dest.writeInt(ownerUid);
         dest.writeString(ownerPackageName);
@@ -450,6 +472,10 @@
         sb.append(physicalYDpi);
         sb.append(") dpi, layerStack ");
         sb.append(layerStack);
+        sb.append(", appVsyncOff ");
+        sb.append(appVsyncOffsetNanos);
+        sb.append(", presDeadline ");
+        sb.append(presentationDeadlineNanos);
         sb.append(", type ");
         sb.append(Display.typeToString(type));
         if (address != null) {
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 424d860..dcd9ba9 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -51,10 +51,10 @@
 
     private int mWidth;
     private int mHeight;
-    
+
     private float[] mPoint;
     private float[] mLine;
-    
+
     private Rect mClipBounds;
     private RectF mPathBounds;
 
@@ -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
     ///////////////////////////////////////////////////////////////////////////
 
@@ -210,7 +167,7 @@
 
         nSetViewport(mRenderer, width, height);
     }
-    
+
     private static native void nSetViewport(long renderer, int width, int height);
 
     @Override
@@ -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
     ///////////////////////////////////////////////////////////////////////////
@@ -265,68 +208,25 @@
 
     /**
      * Must match Caches::FlushMode values
-     * 
-     * @see #flushCaches(int) 
+     *
+     * @see #flushCaches(int)
      */
     static final int FLUSH_CACHES_LAYERS = 0;
-    
+
     /**
      * Must match Caches::FlushMode values
-     * 
-     * @see #flushCaches(int) 
+     *
+     * @see #flushCaches(int)
      */
     static final int FLUSH_CACHES_MODERATE = 1;
 
     /**
      * Must match Caches::FlushMode values
-     * 
-     * @see #flushCaches(int) 
+     *
+     * @see #flushCaches(int)
      */
     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
     ///////////////////////////////////////////////////////////////////////////
@@ -334,18 +234,17 @@
     protected static native long nFinishRecording(long renderer);
 
     @Override
-    public int drawDisplayList(RenderNode displayList, Rect dirty, int flags) {
-        return nDrawDisplayList(mRenderer, displayList.getNativeDisplayList(),
-                dirty, flags);
+    public int drawRenderNode(RenderNode renderNode, Rect dirty, int flags) {
+        return nDrawRenderNode(mRenderer, renderNode.getNativeDisplayList(), dirty, flags);
     }
 
-    private static native int nDrawDisplayList(long renderer, long displayList,
+    private static native int nDrawRenderNode(long renderer, long renderNode,
             Rect dirty, int flags);
 
     ///////////////////////////////////////////////////////////////////////////
     // Hardware layer
     ///////////////////////////////////////////////////////////////////////////
-    
+
     void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
         layer.setLayerPaint(paint);
         nDrawLayer(mRenderer, layer.getLayer(), x, y);
@@ -398,7 +297,7 @@
     public boolean clipRect(float left, float top, float right, float bottom) {
         return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
     }
-    
+
     private static native boolean nClipRect(long renderer, float left, float top,
             float right, float bottom, int op);
 
@@ -411,14 +310,14 @@
     public boolean clipRect(int left, int top, int right, int bottom) {
         return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
     }
-    
+
     private static native boolean nClipRect(long renderer, int left, int top,
             int right, int bottom, int op);
 
     @Override
     public boolean clipRect(Rect rect) {
         return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
-                Region.Op.INTERSECT.nativeInt);        
+                Region.Op.INTERSECT.nativeInt);
     }
 
     @Override
@@ -460,7 +359,7 @@
     public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
         return nQuickReject(mRenderer, left, top, right, bottom);
     }
-    
+
     private static native boolean nQuickReject(long renderer, float left, float top,
             float right, float bottom);
 
@@ -485,7 +384,7 @@
     public void translate(float dx, float dy) {
         if (dx != 0.0f || dy != 0.0f) nTranslate(mRenderer, dx, dy);
     }
-    
+
     private static native void nTranslate(long renderer, float dx, float dy);
 
     @Override
@@ -499,21 +398,24 @@
     public void rotate(float degrees) {
         nRotate(mRenderer, degrees);
     }
-    
+
     private static native void nRotate(long renderer, float degrees);
 
     @Override
     public void scale(float sx, float sy) {
+        // TODO: remove
+        if (sx > 1000000 || sy > 1000000) throw new IllegalArgumentException("invalid scales passed " + sx + ", " + sy);
+
         nScale(mRenderer, sx, sy);
     }
-    
+
     private static native void nScale(long renderer, float sx, float sy);
 
     @Override
     public void setMatrix(Matrix matrix) {
         nSetMatrix(mRenderer, matrix == null ? 0 : matrix.native_instance);
     }
-    
+
     private static native void nSetMatrix(long renderer, long matrix);
 
     @SuppressWarnings("deprecation")
@@ -521,16 +423,16 @@
     public void getMatrix(Matrix matrix) {
         nGetMatrix(mRenderer, matrix.native_instance);
     }
-    
+
     private static native void nGetMatrix(long renderer, long matrix);
 
     @Override
     public void concat(Matrix matrix) {
         if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance);
     }
-    
+
     private static native void nConcatMatrix(long renderer, long matrix);
-    
+
     ///////////////////////////////////////////////////////////////////////////
     // State management
     ///////////////////////////////////////////////////////////////////////////
@@ -539,14 +441,14 @@
     public int save() {
         return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
     }
-    
+
     @Override
     public int save(int saveFlags) {
         return nSave(mRenderer, saveFlags);
     }
 
     private static native int nSave(long renderer, int flags);
-    
+
     @Override
     public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
         if (bounds != null) {
@@ -594,12 +496,12 @@
 
     private static native int nSaveLayerAlpha(long renderer, float left, float top, float right,
             float bottom, int alpha, int saveFlags);
-    
+
     @Override
     public void restore() {
         nRestore(mRenderer);
     }
-    
+
     private static native void nRestore(long renderer);
 
     @Override
@@ -608,12 +510,12 @@
     }
 
     private static native void nRestoreToCount(long renderer, int saveCount);
-    
+
     @Override
     public int getSaveCount() {
         return nGetSaveCount(mRenderer);
     }
-    
+
     private static native int nGetSaveCount(long renderer);
 
     ///////////////////////////////////////////////////////////////////////////
@@ -644,9 +546,9 @@
     ///////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
-            Paint paint) {
-        nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom,
+    public void drawArc(float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
+        nDrawArc(mRenderer, left, top, right, bottom,
                 startAngle, sweepAngle, useCenter, paint.mNativePaint);
     }
 
@@ -839,7 +741,7 @@
     public void drawColor(int color, PorterDuff.Mode mode) {
         nDrawColor(mRenderer, color, mode.nativeInt);
     }
-    
+
     private static native void nDrawColor(long renderer, int color, int mode);
 
     @Override
@@ -871,8 +773,8 @@
     }
 
     @Override
-    public void drawOval(RectF oval, Paint paint) {
-        nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
+    public void drawOval(float left, float top, float right, float bottom, Paint paint) {
+        nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint);
     }
 
     private static native void nDrawOval(long renderer, float left, float top,
@@ -899,28 +801,14 @@
     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) {
-            return;
-        }
-
         picture.endRecording();
         // TODO: Implement rendering
     }
 
     @Override
     public void drawPicture(Picture picture, Rect dst) {
-        if (picture.createdFromStream) {
-            return;
-        }
-
         save();
         translate(dst.left, dst.top);
         if (picture.getWidth() > 0 && picture.getHeight() > 0) {
@@ -932,10 +820,6 @@
 
     @Override
     public void drawPicture(Picture picture, RectF dst) {
-        if (picture.createdFromStream) {
-            return;
-        }
-
         save();
         translate(dst.left, dst.top);
         if (picture.getWidth() > 0 && picture.getHeight() > 0) {
@@ -968,31 +852,7 @@
     private static native void nDrawPoints(long renderer, float[] points,
             int offset, int count, long paint);
 
-    @SuppressWarnings("deprecation")
-    @Override
-    public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
-        if (index < 0 || index + count > text.length || count * 2 > pos.length) {
-            throw new IndexOutOfBoundsException();
-        }
-
-        nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint);
-    }
-
-    private static native void nDrawPosText(long renderer, char[] text, int index, int count,
-            float[] pos, long paint);
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void drawPosText(String text, float[] pos, Paint paint) {
-        if (text.length() * 2 > pos.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-
-        nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint);
-    }
-
-    private static native void nDrawPosText(long renderer, String text, int start, int end,
-            float[] pos, long paint);
+    // Note: drawPosText just uses implementation in Canvas
 
     @Override
     public void drawRect(float left, float top, float right, float bottom, Paint paint) {
@@ -1036,7 +896,7 @@
         nDrawText(mRenderer, text, index, count, x, y,
                 paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
     }
-    
+
     private static native void nDrawText(long renderer, char[] text, int index, int count,
             float x, float y, int bidiFlags, long paint, long typeface);
 
@@ -1084,68 +944,66 @@
         }
 
         nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
-                paint.mBidiFlags, paint.mNativePaint);
+                paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
     }
 
     private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count,
-            long path, float hOffset, float vOffset, int bidiFlags, long nativePaint);
+            long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
+            long typeface);
 
     @Override
     public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
         if (text.length() == 0) return;
 
         nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
-                paint.mBidiFlags, paint.mNativePaint);
+                paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
     }
 
     private static native void nDrawTextOnPath(long renderer, String text, int start, int end,
-            long path, float hOffset, float vOffset, int bidiFlags, long nativePaint);
+            long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
+            long typeface);
 
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, int dir, Paint paint) {
+            float x, float y, boolean isRtl, Paint paint) {
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
-            throw new IllegalArgumentException("Unknown direction: " + dir);
-        }
 
-        nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
+        nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, isRtl,
                 paint.mNativePaint, paint.mNativeTypeface);
     }
 
     private static native void nDrawTextRun(long renderer, char[] text, int index, int count,
-            int contextIndex, int contextCount, float x, float y, int dir, long nativePaint, long nativeTypeface);
+            int contextIndex, int contextCount, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
 
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, int dir, Paint paint) {
+            float x, float y, boolean isRtl, Paint paint) {
         if ((start | end | end - start | text.length() - end) < 0) {
             throw new IndexOutOfBoundsException();
         }
 
-        int flags = dir == 0 ? 0 : 1;
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
-                    contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+                    contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawTextRun(this, start, end,
-                    contextStart, contextEnd, x, y, flags, paint);
+                    contextStart, contextEnd, x, y, isRtl, paint);
         } else {
             int contextLen = contextEnd - contextStart;
             int len = end - start;
             char[] buf = TemporaryBuffer.obtain(contextLen);
             TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
             nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
-                    x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+                    x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
             TemporaryBuffer.recycle(buf);
         }
     }
 
     private static native void nDrawTextRun(long renderer, String text, int start, int end,
-            int contextStart, int contextEnd, float x, float y, int flags, long nativePaint, long nativeTypeface);
+            int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
 
     @Override
     public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
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..263ebda 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -61,16 +61,16 @@
      * Draws the specified display list onto this canvas. The display list can only
      * be drawn if {@link android.view.RenderNode#isValid()} returns true.
      *
-     * @param displayList The display list to replay.
+     * @param renderNode The RenderNode to replay.
      */
-    public void drawDisplayList(RenderNode displayList) {
-        drawDisplayList(displayList, null, RenderNode.FLAG_CLIP_CHILDREN);
+    public void drawRenderNode(RenderNode renderNode) {
+        drawRenderNode(renderNode, null, RenderNode.FLAG_CLIP_CHILDREN);
     }
 
     /**
      * Draws the specified display list onto this canvas.
      *
-     * @param displayList The display list to replay.
+     * @param renderNode The RenderNode to replay.
      * @param dirty Ignored, can be null.
      * @param flags Optional flags about drawing, see {@link RenderNode} for
      *              the possible flags.
@@ -80,7 +80,7 @@
      *
      * @hide
      */
-    public abstract int drawDisplayList(RenderNode displayList, Rect dirty, int flags);
+    public abstract int drawRenderNode(RenderNode renderNode, Rect dirty, int flags);
 
     /**
      * Draws the specified layer onto this canvas.
@@ -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..266a6fed 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -19,7 +19,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 
 import com.android.internal.util.VirtualRefBasePtr;
@@ -34,34 +33,18 @@
  * @hide
  */
 final class HardwareLayer {
-    private static final int LAYER_TYPE_TEXTURE = 1;
-    private static final int LAYER_TYPE_DISPLAY_LIST = 2;
-
     private HardwareRenderer mRenderer;
     private VirtualRefBasePtr mFinalizer;
-    private RenderNode mDisplayList;
-    private final int mLayerType;
 
-    private HardwareLayer(HardwareRenderer renderer, long deferredUpdater, int type) {
+    private HardwareLayer(HardwareRenderer renderer, long deferredUpdater) {
         if (renderer == null || deferredUpdater == 0) {
             throw new IllegalArgumentException("Either hardware renderer: " + renderer
                     + " or deferredUpdater: " + deferredUpdater + " is invalid");
         }
         mRenderer = renderer;
-        mLayerType = type;
         mFinalizer = new VirtualRefBasePtr(deferredUpdater);
     }
 
-    private void assertType(int type) {
-        if (mLayerType != type) {
-            throw new IllegalAccessError("Method not appropriate for this layer type! " + mLayerType);
-        }
-    }
-
-    boolean hasDisplayList() {
-        return mDisplayList != null;
-    }
-
     /**
      * Update the paint used when drawing this layer.
      *
@@ -90,11 +73,6 @@
             // Already destroyed
             return;
         }
-
-        if (mDisplayList != null) {
-            mDisplayList.destroyDisplayListData();
-            mDisplayList = null;
-        }
         mRenderer.onLayerDestroyed(this);
         mRenderer = null;
         mFinalizer.release();
@@ -105,21 +83,6 @@
         return mFinalizer.get();
     }
 
-    public RenderNode startRecording() {
-        assertType(LAYER_TYPE_DISPLAY_LIST);
-
-        if (mDisplayList == null) {
-            mDisplayList = RenderNode.create("HardwareLayer");
-        }
-        return mDisplayList;
-    }
-
-    public void endRecording(Rect dirtyRect) {
-        nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(),
-                dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
-        mRenderer.pushLayerUpdate(this);
-    }
-
     /**
      * Copies this layer into the specified bitmap.
      *
@@ -160,7 +123,6 @@
      * Indicates that this layer has lost its texture.
      */
     public void detachSurfaceTexture(final SurfaceTexture surface) {
-        assertType(LAYER_TYPE_TEXTURE);
         mRenderer.safelyRun(new Runnable() {
             @Override
             public void run() {
@@ -172,36 +134,16 @@
         });
     }
 
-    /**
-     * 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());
     }
 
     public void setSurfaceTexture(SurfaceTexture surface) {
-        assertType(LAYER_TYPE_TEXTURE);
         nSetSurfaceTexture(mFinalizer.get(), surface, false);
         mRenderer.pushLayerUpdate(this);
     }
 
     public void updateSurfaceTexture() {
-        assertType(LAYER_TYPE_TEXTURE);
         nUpdateSurfaceTexture(mFinalizer.get());
         mRenderer.pushLayerUpdate(this);
     }
@@ -210,39 +152,15 @@
      * This should only be used by HardwareRenderer! Do not call directly
      */
     SurfaceTexture createSurfaceTexture() {
-        assertType(LAYER_TYPE_TEXTURE);
         SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get()));
         nSetSurfaceTexture(mFinalizer.get(), st, true);
         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);
+        return new HardwareLayer(renderer, layer);
     }
 
-    /**
-     * 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 +172,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..cfb4af2 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
@@ -171,9 +172,6 @@
      */
     public static boolean sSystemRendererDisabled = false;
 
-    /** @hide */
-    public static boolean sUseRenderThread = true;
-
     private boolean mEnabled;
     private boolean mRequested = true;
 
@@ -309,7 +307,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 +364,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
@@ -378,16 +375,6 @@
     abstract HardwareLayer createTextureLayer();
 
     /**
-     * Creates a new hardware layer.
-     *
-     * @param width The minimum width of the layer
-     * @param height The minimum height of the layer
-     *
-     * @return A hardware layer
-     */
-    abstract HardwareLayer createDisplayListLayer(int width, int height);
-
-    /**
      * Creates a new {@link SurfaceTexture} that can be used to render into the
      * specified hardware layer.
      *
@@ -400,17 +387,6 @@
     abstract boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap);
 
     /**
-     * Schedules the functor for execution in either kModeProcess or
-     * kModeProcessNoContext, depending on whether or not there is an EGLContext.
-     *
-     * @param functor The native functor to invoke
-     * @param waitForCompletion If true, this will not return until the functor
-     *                          has invoked. If false, the functor may be invoked
-     *                          asynchronously.
-     */
-    abstract void invokeFunctor(long functor, boolean waitForCompletion);
-
-    /**
      * Initializes the hardware renderer for the specified surface and setup the
      * renderer for drawing, if needed. This is invoked when the ViewAncestor has
      * potentially lost the hardware renderer. The hardware renderer should be
@@ -466,14 +442,10 @@
      *
      * @return A hardware renderer backed by OpenGL.
      */
-    static HardwareRenderer create(boolean translucent) {
+    static HardwareRenderer create(Context context, boolean translucent) {
         HardwareRenderer renderer = null;
         if (GLES20Canvas.isAvailable()) {
-            if (sUseRenderThread) {
-                renderer = new ThreadedRenderer(translucent);
-            } else {
-                renderer = new GLRenderer(translucent);
-            }
+            renderer = new ThreadedRenderer(context, translucent);
         }
         return renderer;
     }
@@ -500,7 +472,7 @@
      *              see {@link android.content.ComponentCallbacks}
      */
     static void startTrimMemory(int level) {
-        GLRenderer.startTrimMemory(level);
+        ThreadedRenderer.startTrimMemory(level);
     }
 
     /**
@@ -508,7 +480,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/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index b9ed801..577415e 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -311,7 +311,7 @@
         if (mFactory == null) {
             mFactory = mFactory2 = factory;
         } else {
-            mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2);
+            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
         }
     }
 
@@ -319,7 +319,11 @@
      * @hide for use by framework
      */
     public void setPrivateFactory(Factory2 factory) {
-        mPrivateFactory = factory;
+        if (mPrivateFactory == null) {
+            mPrivateFactory = factory;
+        } else {
+            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
+        }
     }
 
     /**
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index e63829e..ddc185c 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -19,9 +19,7 @@
 import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.Outline;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.graphics.Paint;
 
 /**
  * <p>A display list records a series of graphics related operations and can replay
@@ -179,12 +177,6 @@
     private boolean mValid;
     private final long mNativeRenderNode;
 
-    // We need to keep a strong reference to all running animators to ensure that
-    // they can call removeAnimator when they have finished, as the native-side
-    // object can only hold a WeakReference<> to avoid leaking memory due to
-    // cyclic references.
-    private List<RenderNodeAnimator> mActiveAnimators;
-
     private RenderNode(String name) {
         mNativeRenderNode = nCreate(name);
     }
@@ -322,11 +314,13 @@
      * handled in the drawLayer operation directly (and more efficiently).
      *
      * @param caching true if the display list represents a hardware layer, false otherwise.
-     *
-     * @hide
      */
-    public void setCaching(boolean caching) {
-        nSetCaching(mNativeRenderNode, caching);
+    public boolean setLayerType(int layerType) {
+        return nSetLayerType(mNativeRenderNode, layerType);
+    }
+
+    public boolean setLayerPaint(Paint paint) {
+        return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.mNativePaint : 0);
     }
 
     /**
@@ -335,8 +329,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 +340,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 +349,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 +359,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 +376,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 +387,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 +398,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 +412,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 +425,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 +451,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 +467,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 +483,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 +504,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 +523,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 +544,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 +565,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 +586,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 +607,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 +628,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 +649,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 +670,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 +697,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 +718,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 +739,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 +760,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 +781,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 +807,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 +819,8 @@
      *
      * @see View#offsetLeftAndRight(int)
      */
-    public void offsetLeftAndRight(float offset) {
-        nOffsetLeftAndRight(mNativeRenderNode, offset);
+    public boolean offsetLeftAndRight(float offset) {
+        return nOffsetLeftAndRight(mNativeRenderNode, offset);
     }
 
     /**
@@ -836,8 +831,8 @@
      *
      * @see View#offsetTopAndBottom(int)
      */
-    public void offsetTopAndBottom(float offset) {
-        nOffsetTopAndBottom(mNativeRenderNode, offset);
+    public boolean offsetTopAndBottom(float offset) {
+        return nOffsetTopAndBottom(mNativeRenderNode, offset);
     }
 
     /**
@@ -862,18 +857,9 @@
     ///////////////////////////////////////////////////////////////////////////
 
     public void addAnimator(RenderNodeAnimator animator) {
-        if (mActiveAnimators == null) {
-            mActiveAnimators = new ArrayList<RenderNodeAnimator>();
-        }
-        mActiveAnimators.add(animator);
         nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
     }
 
-    public void removeAnimator(RenderNodeAnimator animator) {
-        nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator());
-        mActiveAnimators.remove(animator);
-    }
-
     ///////////////////////////////////////////////////////////////////////////
     // Native methods
     ///////////////////////////////////////////////////////////////////////////
@@ -890,42 +876,43 @@
 
     // 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 nSetLayerType(long renderNode, int layerType);
+    private static native boolean nSetLayerPaint(long renderNode, long paint);
+    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);
@@ -955,7 +942,6 @@
     ///////////////////////////////////////////////////////////////////////////
 
     private static native void nAddAnimator(long renderNode, long animatorPtr);
-    private static native void nRemoveAnimator(long renderNode, long animatorPtr);
 
     ///////////////////////////////////////////////////////////////////////////
     // Finalization
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 4979059..1363a5c 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -172,12 +172,14 @@
 
     @Override
     public void cancel() {
-        mTarget.removeAnimator(this);
+        if (!mFinished) {
+            nCancel(mNativePtr.get());
 
-        final ArrayList<AnimatorListener> listeners = getListeners();
-        final int numListeners = listeners == null ? 0 : listeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            listeners.get(i).onAnimationCancel(this);
+            final ArrayList<AnimatorListener> listeners = getListeners();
+            final int numListeners = listeners == null ? 0 : listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                listeners.get(i).onAnimationCancel(this);
+            }
         }
     }
 
@@ -219,10 +221,6 @@
         return mTarget;
     }
 
-    /**
-     * WARNING: May only be called once!!!
-     * TODO: Fix above -_-
-     */
     public void setStartValue(float startValue) {
         checkMutable();
         nSetStartValue(mNativePtr.get(), startValue);
@@ -231,6 +229,9 @@
     @Override
     public void setStartDelay(long startDelay) {
         checkMutable();
+        if (startDelay < 0) {
+            throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+        }
         nSetStartDelay(mNativePtr.get(), startDelay);
     }
 
@@ -242,6 +243,9 @@
     @Override
     public RenderNodeAnimator setDuration(long duration) {
         checkMutable();
+        if (duration < 0) {
+            throw new IllegalArgumentException("duration must be positive; " + duration);
+        }
         nSetDuration(mNativePtr.get(), duration);
         return this;
     }
@@ -269,7 +273,6 @@
 
     private void onFinished() {
         mFinished = true;
-        mTarget.removeAnimator(this);
 
         final ArrayList<AnimatorListener> listeners = getListeners();
         final int numListeners = listeners == null ? 0 : listeners.size();
@@ -296,10 +299,13 @@
             long canvasProperty, float finalValue);
     private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
             long canvasProperty, int paintField, float finalValue);
+
     private static native void nSetStartValue(long nativePtr, float startValue);
     private static native void nSetDuration(long nativePtr, long duration);
     private static native long nGetDuration(long nativePtr);
     private static native void nSetStartDelay(long nativePtr, long startDelay);
     private static native long nGetStartDelay(long nativePtr);
     private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+
+    private static native void nCancel(long animPtr);
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c15ce44..94d8f70 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;
 
 
     /**
@@ -439,6 +458,8 @@
         public float xDpi;
         public float yDpi;
         public boolean secure;
+        public long appVsyncOffsetNanos;
+        public long presentationDeadlineNanos;
 
         public PhysicalDisplayInfo() {
         }
@@ -460,7 +481,9 @@
                     && density == other.density
                     && xDpi == other.xDpi
                     && yDpi == other.yDpi
-                    && secure == other.secure;
+                    && secure == other.secure
+                    && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+                    && presentationDeadlineNanos == other.presentationDeadlineNanos;
         }
 
         @Override
@@ -476,6 +499,8 @@
             xDpi = other.xDpi;
             yDpi = other.yDpi;
             secure = other.secure;
+            appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+            presentationDeadlineNanos = other.presentationDeadlineNanos;
         }
 
         // For debugging purposes
@@ -483,22 +508,16 @@
         public String toString() {
             return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
                     + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
-                    + "}";
+                    + ", appVsyncOffset " + appVsyncOffsetNanos
+                    + ", bufferDeadline " + presentationDeadlineNanos + "}";
         }
     }
 
-    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 +616,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 +632,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 +642,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 +653,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 +670,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 +695,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 +707,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/TextureView.java b/core/java/android/view/TextureView.java
index 2a9f7d5..1f7eaa2 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -275,6 +275,11 @@
         }
     }
 
+    @Override
+    public void setLayerPaint(Paint paint) {
+        setLayerType(/* ignored */ 0, paint);
+    }
+
     /**
      * Always returns {@link #LAYER_TYPE_HARDWARE}.
      */
@@ -332,11 +337,6 @@
         }
     }
 
-    @Override
-    boolean destroyLayer(boolean valid) {
-        return false;
-    }
-
     /**
      * @hide
      */
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 72b9d3e..bfab654 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,21 +16,29 @@
 
 package android.view;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.graphics.drawable.Drawable;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.Log;
+import android.util.LongSparseArray;
 import android.util.TimeUtils;
 import android.view.Surface.OutOfResourcesException;
 import android.view.View.AttachInfo;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
 
 /**
  * Hardware renderer that proxies the rendering to a render thread. Most calls
@@ -54,13 +62,11 @@
 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;
+    private static final int SYNC_OK = 0;
     // Needs a ViewRoot invalidate
-    private static final int SYNC_INVALIDATE_REQUIRED = 0x1;
+    private static final int SYNC_INVALIDATE_REQUIRED = 1 << 0;
 
     private static final String[] VISUALIZERS = {
         PROFILE_PROPERTY_VISUALIZE_BARS,
@@ -73,15 +79,14 @@
     private Choreographer mChoreographer;
     private boolean mProfilingEnabled;
 
-    ThreadedRenderer(boolean translucent) {
-        // Temporarily disabled
-        //AtlasInitializer.sInstance.init();
-
+    ThreadedRenderer(Context context, boolean translucent) {
         long rootNodePtr = nCreateRootRenderNode();
         mRootNode = RenderNode.adopt(rootNodePtr);
         mRootNode.setClipToBounds(false);
         mNativeProxy = nCreateProxy(translucent, rootNodePtr);
 
+        AtlasInitializer.sInstance.init(context, mNativeProxy);
+
         // Setup timing
         mChoreographer = Choreographer.getInstance();
         nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
@@ -217,7 +222,7 @@
         try {
             canvas.save();
             callbacks.onHardwarePreDraw(canvas);
-            canvas.drawDisplayList(view.getDisplayList());
+            canvas.drawRenderNode(view.getDisplayList());
             callbacks.onHardwarePostDraw(canvas);
             canvas.restore();
         } finally {
@@ -229,7 +234,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,26 +252,15 @@
 
         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();
         }
     }
 
-    @Override
-    void invokeFunctor(long functor, boolean waitForCompletion) {
-        nInvokeFunctor(mNativeProxy, functor, waitForCompletion);
-    }
-
-    @Override
-    HardwareLayer createDisplayListLayer(int width, int height) {
-        long layer = nCreateDisplayListLayer(mNativeProxy, width, height);
-        return HardwareLayer.adoptDisplayListLayer(this, layer);
+    static void invokeFunctor(long functor, boolean waitForCompletion) {
+        nInvokeFunctor(functor, waitForCompletion);
     }
 
     @Override
@@ -332,6 +326,14 @@
         }
     }
 
+    static void startTrimMemory(int level) {
+        // TODO
+    }
+
+    static void endTrimMemory() {
+        // TODO
+    }
+
     private static class AtlasInitializer {
         static AtlasInitializer sInstance = new AtlasInitializer();
 
@@ -339,7 +341,7 @@
 
         private AtlasInitializer() {}
 
-        synchronized void init() {
+        synchronized void init(Context context, long renderProxy) {
             if (mInitialized) return;
             IBinder binder = ServiceManager.getService("assetatlas");
             if (binder == null) return;
@@ -351,7 +353,9 @@
                     if (buffer != null) {
                         long[] map = atlas.getMap();
                         if (map != null) {
-                            nSetAtlas(buffer, map);
+                            // TODO Remove after fixing b/15425820
+                            validateMap(context, map);
+                            nSetAtlas(renderProxy, buffer, map);
                             mInitialized = true;
                         }
                         // If IAssetAtlas is not the same class as the IBinder
@@ -366,9 +370,35 @@
                 Log.w(LOG_TAG, "Could not acquire atlas", e);
             }
         }
+
+        private static void validateMap(Context context, long[] map) {
+            Log.d("Atlas", "Validating map...");
+            HashSet<Long> preloadedPointers = new HashSet<Long>();
+
+            // We only care about drawables that hold bitmaps
+            final Resources resources = context.getResources();
+            final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
+
+            final int count = drawables.size();
+            for (int i = 0; i < count; i++) {
+                final Bitmap bitmap = drawables.valueAt(i).getBitmap();
+                if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) {
+                    preloadedPointers.add(bitmap.mNativeBitmap);
+                }
+            }
+
+            for (int i = 0; i < map.length; i += 4) {
+                if (!preloadedPointers.contains(map[i])) {
+                    Log.w("Atlas", String.format("Pointer 0x%X, not in getPreloadedDrawables?", map[i]));
+                    map[i] = 0;
+                }
+            }
+        }
     }
 
-    private static native void nSetAtlas(GraphicBuffer buffer, long[] map);
+    static native void setupShadersDiskCache(String cacheFile);
+
+    private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
 
     private static native long nCreateRootRenderNode();
     private static native long nCreateProxy(boolean translucent, long rootRenderNode);
@@ -384,12 +414,11 @@
             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);
 
-    private static native void nInvokeFunctor(long nativeProxy, long functor, boolean waitForCompletion);
+    private static native void nInvokeFunctor(long functor, boolean waitForCompletion);
 
     private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height);
     private static native long nCreateTextureLayer(long nativeProxy);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7c70ee4..829e089 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -90,6 +91,7 @@
 import com.android.internal.R;
 import com.android.internal.util.Predicate;
 import com.android.internal.view.menu.MenuBuilder;
+
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
@@ -431,7 +433,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>
@@ -670,7 +672,7 @@
  * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
  * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
  * @attr ref android.R.styleable#View_stateListAnimator
- * @attr ref android.R.styleable#View_viewName
+ * @attr ref android.R.styleable#View_transitionName
  * @attr ref android.R.styleable#View_soundEffectsEnabled
  * @attr ref android.R.styleable#View_tag
  * @attr ref android.R.styleable#View_textAlignment
@@ -2775,6 +2777,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 +2791,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
@@ -3179,20 +3188,23 @@
 
     @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_")
     private Drawable mBackground;
+    private ColorStateList mBackgroundTint = null;
+    private PorterDuff.Mode mBackgroundTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasBackgroundTint = false;
 
     /**
-     * Display list used for backgrounds.
+     * RenderNode used for backgrounds.
      * <p>
      * When non-null and valid, this is expected to contain an up-to-date copy
-     * of the background drawable. It is cleared on temporary detach and reset
+     * of the background drawable. It is cleared on temporary detach, and reset
      * on cleanup.
      */
-    private RenderNode mBackgroundDisplayList;
+    private RenderNode mBackgroundRenderNode;
 
     private int mBackgroundResource;
     private boolean mBackgroundSizeChanged;
 
-    private String mViewName;
+    private String mTransitionName;
 
     static class ListenerInfo {
         /**
@@ -3465,8 +3477,6 @@
     })
     int mLayerType = LAYER_TYPE_NONE;
     Paint mLayerPaint;
-    Rect mLocalDirtyRect;
-    private HardwareLayer mHardwareLayer;
 
     /**
      * Set to true when drawing cache is enabled and cannot be created.
@@ -3884,7 +3894,7 @@
                     if (fadingEdge != FADING_EDGE_NONE) {
                         viewFlagValues |= fadingEdge;
                         viewFlagMasks |= FADING_EDGE_MASK;
-                        initializeFadingEdge(a);
+                        initializeFadingEdgeInternal(a);
                     }
                     break;
                 case R.styleable.View_scrollbarStyle:
@@ -4006,8 +4016,8 @@
                 case R.styleable.View_accessibilityLiveRegion:
                     setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
                     break;
-                case R.styleable.View_viewName:
-                    setViewName(a.getString(attr));
+                case R.styleable.View_transitionName:
+                    setTransitionName(a.getString(attr));
                     break;
                 case R.styleable.View_nestedScrollingEnabled:
                     setNestedScrollingEnabled(a.getBoolean(attr, false));
@@ -4016,6 +4026,16 @@
                     setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
                             a.getResourceId(attr, 0)));
                     break;
+                case R.styleable.View_backgroundTint:
+                    // This will get applied later during setBackground().
+                    mBackgroundTint = a.getColorStateList(R.styleable.View_backgroundTint);
+                    mHasBackgroundTint = true;
+                    break;
+                case R.styleable.View_backgroundTintMode:
+                    // This will get applied later during setBackground().
+                    mBackgroundTintMode = Drawable.parseTintMode(a.getInt(
+                            R.styleable.View_backgroundTintMode, -1), mBackgroundTintMode);
+                    break;
             }
         }
 
@@ -4088,7 +4108,7 @@
         }
 
         if (initializeScrollbars) {
-            initializeScrollbars(a);
+            initializeScrollbarsInternal(a);
         }
 
         a.recycle();
@@ -4216,6 +4236,32 @@
      * @param a the styled attributes set to initialize the fading edges from
      */
     protected void initializeFadingEdge(TypedArray a) {
+        // This method probably shouldn't have been included in the SDK to begin with.
+        // It relies on 'a' having been initialized using an attribute filter array that is
+        // not publicly available to the SDK. The old method has been renamed
+        // to initializeFadingEdgeInternal and hidden for framework use only;
+        // this one initializes using defaults to make it safe to call for apps.
+
+        TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+        initializeFadingEdgeInternal(arr);
+
+        arr.recycle();
+    }
+
+    /**
+     * <p>
+     * Initializes the fading edges from a given set of styled attributes. This
+     * method should be called by subclasses that need fading edges and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the fading edges from
+     * @hide This is the real method; the public one is shimmed to be safe to call from apps.
+     */
+    protected void initializeFadingEdgeInternal(TypedArray a) {
         initScrollCache();
 
         mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
@@ -4330,6 +4376,31 @@
      * @param a the styled attributes set to initialize the scrollbars from
      */
     protected void initializeScrollbars(TypedArray a) {
+        // It's not safe to use this method from apps. The parameter 'a' must have been obtained
+        // using the View filter array which is not available to the SDK. As such, internal
+        // framework usage now uses initializeScrollbarsInternal and we grab a default
+        // TypedArray with the right filter instead here.
+        TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+        initializeScrollbarsInternal(arr);
+
+        // We ignored the method parameter. Recycle the one we actually did use.
+        arr.recycle();
+    }
+
+    /**
+     * <p>
+     * Initializes the scrollbars from a given set of styled attributes. This
+     * method should be called by subclasses that need scrollbars and when an
+     * instance of these subclasses is created programmatically rather than
+     * being inflated from XML. This method is automatically called when the XML
+     * is inflated.
+     * </p>
+     *
+     * @param a the styled attributes set to initialize the scrollbars from
+     * @hide
+     */
+    protected void initializeScrollbarsInternal(TypedArray a) {
         initScrollCache();
 
         final ScrollabilityCache scrollabilityCache = mScrollCache;
@@ -4789,14 +4860,10 @@
      * @param v previous or the next focus holder, or null if none
      */
     private void manageFocusHotspot(boolean focused, View v) {
-        if (mBackground == null) {
-            return;
-        }
-
         final Rect r = new Rect();
-        if (!focused && v != null) {
+        if (!focused && v != null && mAttachInfo != null) {
             v.getBoundsOnScreen(r);
-            final int[] location = new int[2];
+            final int[] location = mAttachInfo.mTmpLocation;
             getLocationOnScreen(location);
             r.offset(-location[0], -location[1]);
         } else {
@@ -4805,7 +4872,7 @@
 
         final float x = r.exactCenterX();
         final float y = r.exactCenterY();
-        mBackground.setHotspot(x, y);
+        drawableHotspotChanged(x, y);
     }
 
     /**
@@ -5377,8 +5444,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;
         }
@@ -6100,6 +6168,12 @@
      */
     protected boolean fitSystemWindows(Rect insets) {
         if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
+            if (insets == null) {
+                // Null insets by definition have already been consumed.
+                // This call cannot apply insets since there are none to apply,
+                // so return false.
+                return false;
+            }
             // If we're not in the process of dispatching the newer apply insets call,
             // that means we're not in the compatibility path. Dispatch into the newer
             // apply insets path and take things from there.
@@ -6761,7 +6835,7 @@
      */
     private void setPressed(boolean pressed, float x, float y) {
         if (pressed) {
-            setHotspot(x, y);
+            drawableHotspotChanged(x, y);
         }
 
         setPressed(pressed);
@@ -7648,7 +7722,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
      */
@@ -9100,8 +9174,7 @@
                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
-                        setHotspot(x, y);
-                        setPressed(true);
+                        setPressed(true, x, y);
                         checkForLongClick(0);
                     }
                     break;
@@ -9113,7 +9186,7 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    setHotspot(x, y);
+                    drawableHotspotChanged(x, y);
 
                     // Be lenient about moving outside of buttons
                     if (!pointInView(x, y, mTouchSlop)) {
@@ -9135,12 +9208,6 @@
         return false;
     }
 
-    private void setHotspot(float x, float y) {
-        if (mBackground != null) {
-            mBackground.setHotspot(x, y);
-        }
-    }
-
     /**
      * @hide
      */
@@ -9236,6 +9303,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 +9737,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()
@@ -12906,7 +12997,6 @@
         stopNestedScroll();
 
         destroyDrawingCache();
-        destroyLayer(false);
 
         cleanupDraw();
         mCurrentAnimation = null;
@@ -12919,33 +13009,6 @@
         }
     }
 
-    /**
-     * This method ensures the hardware renderer is in a valid state
-     * before executing the specified action.
-     *
-     * This method will attempt to set a valid state even if the window
-     * the renderer is attached to was destroyed.
-     *
-     * This method is not guaranteed to work. If the hardware renderer
-     * does not exist or cannot be put in a valid state, this method
-     * will not executed the specified action.
-     *
-     * The specified action is executed synchronously.
-     *
-     * @param action The action to execute after the renderer is in a valid state
-     *
-     * @return True if the specified Runnable was executed, false otherwise
-     *
-     * @hide
-     */
-    public boolean executeHardwareAction(Runnable action) {
-        //noinspection SimplifiableIfStatement
-        if (mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
-            return mAttachInfo.mHardwareRenderer.safelyRun(action);
-        }
-        return false;
-    }
-
     void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
     }
 
@@ -13384,28 +13447,25 @@
                     + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
         }
 
-        if (layerType == mLayerType) {
+        boolean typeChanged = mRenderNode.setLayerType(layerType);
+
+        if (!typeChanged) {
             setLayerPaint(paint);
             return;
         }
 
         // Destroy any previous software drawing cache if needed
-        switch (mLayerType) {
-            case LAYER_TYPE_HARDWARE:
-                destroyLayer(false);
-                // fall through - non-accelerated views may use software layer mechanism instead
-            case LAYER_TYPE_SOFTWARE:
-                destroyDrawingCache();
-                break;
-            default:
-                break;
+        if (mLayerType == LAYER_TYPE_SOFTWARE) {
+            destroyDrawingCache();
         }
 
         mLayerType = layerType;
-        final boolean layerDisabled = mLayerType == LAYER_TYPE_NONE;
+        final boolean layerDisabled = (mLayerType == LAYER_TYPE_NONE);
         mLayerPaint = layerDisabled ? null : (paint == null ? new Paint() : paint);
-        mLocalDirtyRect = layerDisabled ? null : new Rect();
+        mRenderNode.setLayerPaint(mLayerPaint);
 
+        // draw() behaves differently if we are on a layer, so we need to
+        // invalidate() here
         invalidateParentCaches();
         invalidate(true);
     }
@@ -13440,11 +13500,9 @@
         if (layerType != LAYER_TYPE_NONE) {
             mLayerPaint = paint == null ? new Paint() : paint;
             if (layerType == LAYER_TYPE_HARDWARE) {
-                HardwareLayer layer = getHardwareLayer();
-                if (layer != null) {
-                    layer.setLayerPaint(mLayerPaint);
+                if (mRenderNode.setLayerPaint(mLayerPaint)) {
+                    invalidateViewProperty(false, false);
                 }
-                invalidateViewProperty(false, false);
             } else {
                 invalidate();
             }
@@ -13500,18 +13558,17 @@
             throw new IllegalStateException("This view must be attached to a window first");
         }
 
+        if (getWidth() == 0 || getHeight() == 0) {
+            return;
+        }
+
         switch (mLayerType) {
             case LAYER_TYPE_HARDWARE:
-                getHardwareLayer();
-                // TODO: We need a better way to handle this case
-                // If views have registered pre-draw listeners they need
-                // to be notified before we build the layer. Those listeners
-                // may however rely on other events to happen first so we
-                // cannot just invoke them here until they don't cancel the
-                // current frame
-                if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) {
-                    attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates();
-                }
+                // The only part of a hardware layer we can build in response to
+                // this call is to ensure the display list is up to date.
+                // The actual rendering of the display list into the layer must
+                // be done at playback time
+                updateDisplayListIfDirty();
                 break;
             case LAYER_TYPE_SOFTWARE:
                 buildDrawingCache(true);
@@ -13520,76 +13577,13 @@
     }
 
     /**
-     * <p>Returns a hardware layer that can be used to draw this view again
-     * without executing its draw method.</p>
+     * If this View draws with a HardwareLayer, returns it.
+     * Otherwise returns null
      *
-     * @return A HardwareLayer ready to render, or null if an error occurred.
+     * TODO: Only TextureView uses this, can we eliminate it?
      */
     HardwareLayer getHardwareLayer() {
-        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null ||
-                !mAttachInfo.mHardwareRenderer.isEnabled()) {
-            return null;
-        }
-
-        final int width = mRight - mLeft;
-        final int height = mBottom - mTop;
-
-        if (width == 0 || height == 0) {
-            return null;
-        }
-
-        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
-            if (mHardwareLayer == null) {
-                mHardwareLayer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
-                        width, height);
-                mLocalDirtyRect.set(0, 0, width, height);
-            } else if (mHardwareLayer.isValid()) {
-                // This should not be necessary but applications that change
-                // the parameters of their background drawable without calling
-                // this.setBackground(Drawable) can leave the view in a bad state
-                // (for instance isOpaque() returns true, but the background is
-                // not opaque.)
-                computeOpaqueFlags();
-
-                if (mHardwareLayer.prepare(width, height, isOpaque())) {
-                    mLocalDirtyRect.set(0, 0, width, height);
-                }
-            }
-
-            // 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);
-            mHardwareLayer.endRecording(mLocalDirtyRect);
-            mLocalDirtyRect.setEmpty();
-        }
-
-        return mHardwareLayer;
-    }
-
-    /**
-     * Destroys this View's hardware layer if possible.
-     *
-     * @return True if the layer was destroyed, false otherwise.
-     *
-     * @see #setLayerType(int, android.graphics.Paint)
-     * @see #LAYER_TYPE_HARDWARE
-     */
-    boolean destroyLayer(boolean valid) {
-        if (mHardwareLayer != null) {
-            mHardwareLayer.destroy();
-            mHardwareLayer = null;
-
-            invalidate(true);
-            invalidateParentCaches();
-            return true;
-        }
-        return false;
+        return null;
     }
 
     /**
@@ -13605,7 +13599,6 @@
      */
     protected void destroyHardwareResources() {
         resetDisplayList();
-        destroyLayer(true);
     }
 
     /**
@@ -13700,20 +13693,8 @@
         return !(mAttachInfo == null || mAttachInfo.mHardwareRenderer == null);
     }
 
-    /**
-     * Returns a DisplayList. If the incoming displayList is null, one will be created.
-     * Otherwise, the same display list will be returned (after having been rendered into
-     * along the way, depending on the invalidation state of the view).
-     *
-     * @param renderNode The previous version of this displayList, could be null.
-     * @param isLayer Whether the requester of the display list is a layer. If so,
-     * the view will avoid creating a layer inside the resulting display list.
-     * @return A new or reused DisplayList object.
-     */
-    private void updateDisplayListIfDirty(@NonNull RenderNode renderNode, boolean isLayer) {
-        if (renderNode == null) {
-            throw new IllegalArgumentException("RenderNode must not be null");
-        }
+    private void updateDisplayListIfDirty() {
+        final RenderNode renderNode = mRenderNode;
         if (!canHaveDisplayList()) {
             // can't populate RenderNode, don't try
             return;
@@ -13721,11 +13702,10 @@
 
         if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                 || !renderNode.isValid()
-                || (!isLayer && mRecreateDisplayList)) {
+                || (mRecreateDisplayList)) {
             // Don't need to recreate the display list, just need to tell our
             // children to restore/recreate theirs
             if (renderNode.isValid()
-                    && !isLayer
                     && !mRecreateDisplayList) {
                 mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                 mPrivateFlags &= ~PFLAG_DIRTY_MASK;
@@ -13734,13 +13714,10 @@
                 return; // no work needed
             }
 
-            if (!isLayer) {
-                // If we got here, we're recreating it. Mark it as such to ensure that
-                // we copy in child display lists into ours in drawChild()
-                mRecreateDisplayList = true;
-            }
+            // If we got here, we're recreating it. Mark it as such to ensure that
+            // we copy in child display lists into ours in drawChild()
+            mRecreateDisplayList = true;
 
-            boolean caching = false;
             int width = mRight - mLeft;
             int height = mBottom - mTop;
             int layerType = getLayerType();
@@ -13748,34 +13725,21 @@
             final HardwareCanvas canvas = renderNode.start(width, height);
 
             try {
-                if (!isLayer && layerType != LAYER_TYPE_NONE) {
-                    if (layerType == LAYER_TYPE_HARDWARE) {
-                        final HardwareLayer layer = getHardwareLayer();
-                        if (layer != null && layer.isValid()) {
-                            canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
-                        } else {
-                            canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
-                                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
-                                            Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-                        }
-                        caching = true;
-                    } else {
-                        buildDrawingCache(true);
-                        Bitmap cache = getDrawingCache(true);
-                        if (cache != null) {
-                            canvas.drawBitmap(cache, 0, 0, mLayerPaint);
-                            caching = true;
-                        }
+                final HardwareLayer layer = getHardwareLayer();
+                if (layer != null && layer.isValid()) {
+                    canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+                } else if (layerType == LAYER_TYPE_SOFTWARE) {
+                    buildDrawingCache(true);
+                    Bitmap cache = getDrawingCache(true);
+                    if (cache != null) {
+                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                     }
                 } else {
-
                     computeScroll();
 
                     canvas.translate(-mScrollX, -mScrollY);
-                    if (!isLayer) {
-                        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
-                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
-                    }
+                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
 
                     // Fast path for layouts with no backgrounds
                     if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
@@ -13789,14 +13753,9 @@
                 }
             } finally {
                 renderNode.end(canvas);
-                renderNode.setCaching(caching);
-                if (isLayer) {
-                    renderNode.setLeftTopRightBottom(0, 0, width, height);
-                } else {
-                    setDisplayListProperties(renderNode);
-                }
+                setDisplayListProperties(renderNode);
             }
-        } else if (!isLayer) {
+        } else {
             mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
             mPrivateFlags &= ~PFLAG_DIRTY_MASK;
         }
@@ -13811,7 +13770,7 @@
      * @hide
      */
     public RenderNode getDisplayList() {
-        updateDisplayListIfDirty(mRenderNode, false);
+        updateDisplayListIfDirty();
         return mRenderNode;
     }
 
@@ -13820,8 +13779,8 @@
             mRenderNode.destroyDisplayListData();
         }
 
-        if (mBackgroundDisplayList != null && mBackgroundDisplayList.isValid()) {
-            mBackgroundDisplayList.destroyDisplayListData();
+        if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
+            mBackgroundRenderNode.destroyDisplayListData();
         }
     }
 
@@ -14305,6 +14264,7 @@
      * @return True if the view is attached to a window and the window is
      *         hardware accelerated; false in any other case.
      */
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isHardwareAccelerated() {
         return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
     }
@@ -14535,7 +14495,7 @@
             mPrivateFlags &= ~PFLAG_INVALIDATED;
         }
 
-        RenderNode displayList = null;
+        RenderNode renderNode = null;
         Bitmap cache = null;
         boolean hasDisplayList = false;
         if (caching) {
@@ -14570,12 +14530,12 @@
         }
         useDisplayListProperties &= hasDisplayList;
         if (useDisplayListProperties) {
-            displayList = getDisplayList();
-            if (!displayList.isValid()) {
+            renderNode = getDisplayList();
+            if (!renderNode.isValid()) {
                 // Uncommon, but possible. If a view is removed from the hierarchy during the call
                 // to getDisplayList(), the display list will be marked invalid and we should not
                 // try to use it again.
-                displayList = null;
+                renderNode = null;
                 hasDisplayList = false;
                 useDisplayListProperties = false;
             }
@@ -14629,7 +14589,7 @@
                 if (transformToApply != null) {
                     if (concatMatrix) {
                         if (useDisplayListProperties) {
-                            displayList.setAnimationMatrix(transformToApply.getMatrix());
+                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                         } else {
                             // Undo the scroll translation, apply the transformation matrix,
                             // then redo the scroll translate to get the correct result.
@@ -14672,7 +14632,7 @@
                             layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG;
                         }
                         if (useDisplayListProperties) {
-                            displayList.setAlpha(alpha * getAlpha() * getTransitionAlpha());
+                            renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
                         } else  if (layerType == LAYER_TYPE_NONE) {
                             final int scrollX = hasDisplayList ? 0 : sx;
                             final int scrollY = hasDisplayList ? 0 : sy;
@@ -14704,12 +14664,12 @@
         }
 
         if (!useDisplayListProperties && hasDisplayList) {
-            displayList = getDisplayList();
-            if (!displayList.isValid()) {
+            renderNode = getDisplayList();
+            if (!renderNode.isValid()) {
                 // Uncommon, but possible. If a view is removed from the hierarchy during the call
                 // to getDisplayList(), the display list will be marked invalid and we should not
                 // try to use it again.
-                displayList = null;
+                renderNode = null;
                 hasDisplayList = false;
             }
         }
@@ -14742,7 +14702,7 @@
                     }
                 } else {
                     mPrivateFlags &= ~PFLAG_DIRTY_MASK;
-                    ((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags);
+                    ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
                 }
             }
         } else if (cache != null) {
@@ -15013,12 +14973,12 @@
         // Attempt to use a display list if requested.
         if (canvas.isHardwareAccelerated() && mAttachInfo != null
                 && mAttachInfo.mHardwareRenderer != null) {
-            mBackgroundDisplayList = getDrawableDisplayList(background, mBackgroundDisplayList);
+            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
 
-            final RenderNode displayList = mBackgroundDisplayList;
+            final RenderNode displayList = mBackgroundRenderNode;
             if (displayList != null && displayList.isValid()) {
                 setBackgroundDisplayListProperties(displayList);
-                ((HardwareCanvas) canvas).drawDisplayList(displayList);
+                ((HardwareCanvas) canvas).drawRenderNode(displayList);
                 return;
             }
         }
@@ -15049,30 +15009,30 @@
      * specified Drawable.
      *
      * @param drawable Drawable for which to create a display list
-     * @param displayList Existing display list, or {@code null}
+     * @param renderNode Existing RenderNode, or {@code null}
      * @return A valid display list for the specified drawable
      */
-    private RenderNode getDrawableDisplayList(Drawable drawable, RenderNode displayList) {
-        if (displayList == null) {
-            displayList = RenderNode.create(drawable.getClass().getName());
+    private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
+        if (renderNode == null) {
+            renderNode = RenderNode.create(drawable.getClass().getName());
         }
 
         final Rect bounds = drawable.getBounds();
         final int width = bounds.width();
         final int height = bounds.height();
-        final HardwareCanvas canvas = displayList.start(width, height);
+        final HardwareCanvas canvas = renderNode.start(width, height);
         try {
             drawable.draw(canvas);
         } finally {
-            displayList.end(canvas);
+            renderNode.end(canvas);
         }
 
         // Set up drawable properties that are view-independent.
-        displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
-        displayList.setProjectBackwards(drawable.isProjected());
-        displayList.setProjectionReceiver(true);
-        displayList.setClipToBounds(false);
-        return displayList;
+        renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
+        renderNode.setProjectBackwards(drawable.isProjected());
+        renderNode.setProjectionReceiver(true);
+        renderNode.setClipToBounds(false);
+        return renderNode;
     }
 
     /**
@@ -15582,6 +15542,20 @@
     }
 
     /**
+     * This function is called whenever the drawable hotspot changes.
+     * <p>
+     * Be sure to call through to the superclass when overriding this function.
+     *
+     * @param x hotspot x coordinate
+     * @param y hotspot y coordinate
+     */
+    public void drawableHotspotChanged(float x, float y) {
+        if (mBackground != null) {
+            mBackground.setHotspot(x, y);
+        }
+    }
+
+    /**
      * Call this to force a view to update its drawable state. This will cause
      * drawableStateChanged to be called on this view. Views that are interested
      * in the new state should call getDrawableState.
@@ -15764,7 +15738,7 @@
             return;
         }
 
-        Drawable d= null;
+        Drawable d = null;
         if (resid != 0) {
             d = mContext.getDrawable(resid);
         }
@@ -15840,8 +15814,9 @@
 
             // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
             // if it has a different minimum size, we should layout again
-            if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() ||
-                    mBackground.getMinimumWidth() != background.getMinimumWidth()) {
+            if (mBackground == null
+                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
+                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                 requestLayout = true;
             }
 
@@ -15852,6 +15827,8 @@
             background.setVisible(getVisibility() == VISIBLE, false);
             mBackground = background;
 
+            applyBackgroundTint();
+
             if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                 mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                 mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
@@ -15907,6 +15884,88 @@
     }
 
     /**
+     * Applies a tint to the background drawable.
+     * <p>
+     * Subsequent calls to {@link #setBackground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#View_backgroundTint
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see Drawable#setTint(ColorStateList, PorterDuff.Mode)
+     */
+    private void setBackgroundTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mBackgroundTint = tint;
+        mBackgroundTintMode = tintMode;
+        mHasBackgroundTint = true;
+
+        applyBackgroundTint();
+    }
+
+    /**
+     * Applies a tint to the background drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setBackground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#View_backgroundTint
+     * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setBackgroundTint(@Nullable ColorStateList tint) {
+        setBackgroundTint(tint, mBackgroundTintMode);
+    }
+
+    /**
+     * @return the tint applied to the background drawable
+     * @attr ref android.R.styleable#View_backgroundTint
+     * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getBackgroundTint() {
+        return mBackgroundTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setBackgroundTint(ColorStateList)}} to the background drawable.
+     * The default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #setBackgroundTint(ColorStateList)
+     */
+    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setBackgroundTint(mBackgroundTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the background drawable
+     * @attr ref android.R.styleable#View_backgroundTintMode
+     * @see #setBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getBackgroundTintMode() {
+        return mBackgroundTintMode;
+    }
+
+    private void applyBackgroundTint() {
+        if (mBackground != null && mHasBackgroundTint) {
+            mBackground = mBackground.mutate();
+            mBackground.setTint(mBackgroundTint, mBackgroundTintMode);
+        }
+    }
+
+    /**
      * Sets the padding. The view may add on the space required to display
      * the scrollbars, depending on the style and visibility of the scrollbars.
      * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
@@ -18881,15 +18940,15 @@
     }
 
     /**
-     * Adds all Views that have {@link #getViewName()} non-null to namedElements.
-     * @param namedElements Will contain all Views in the hierarchy having a view name.
+     * Adds all Views that have {@link #getTransitionName()} non-null to namedElements.
+     * @param namedElements Will contain all Views in the hierarchy having a transitionName.
      * @hide
      */
     public void findNamedViews(Map<String, View> namedElements) {
         if (getVisibility() == VISIBLE) {
-            String viewName = getViewName();
-            if (viewName != null) {
-                namedElements.put(viewName, this);
+            String transitionName = getTransitionName();
+            if (transitionName != null) {
+                namedElements.put(transitionName, this);
             }
         }
     }
@@ -19291,10 +19350,18 @@
      * Sets the name of the View to be used to identify Views in Transitions.
      * Names should be unique in the View hierarchy.
      *
-     * @param viewName The name of the View to uniquely identify it for Transitions.
+     * @param transitionName The name of the View to uniquely identify it for Transitions.
      */
-    public final void setViewName(String viewName) {
-        mViewName = viewName;
+    public final void setTransitionName(String transitionName) {
+        mTransitionName = transitionName;
+    }
+
+    /**
+     * To be removed before L release.
+     * @hide
+     */
+    public final void setViewName(String transitionName) {
+        setTransitionName(transitionName);
     }
 
     /**
@@ -19306,11 +19373,17 @@
      * @return The name used of the View to be used to identify Views in Transitions or null
      * if no name has been given.
      */
-    public String getViewName() {
-        return mViewName;
+    public String getTransitionName() {
+        return mTransitionName;
     }
 
     /**
+     * To be removed before L release.
+     * @hide
+     */
+    public String getViewName() { return getTransitionName(); }
+
+    /**
      * Interface definition for a callback to be invoked when a hardware key event is
      * dispatched to this view. The callback will be invoked before the key event is
      * given to the view. This is only useful for hardware keyboards; a software input
@@ -19761,6 +19834,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
          */
@@ -19825,6 +19904,11 @@
          */
         final int[] mInvalidateChildLocation = new int[2];
 
+        /**
+         * Global to the view hierarchy used as a temporary for dealng with
+         * computing absolute on-screen location.
+         */
+        final int[] mTmpLocation = new int[2];
 
         /**
          * Global to the view hierarchy used as a temporary for dealing with
diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java
index 3854f34..29e865f 100644
--- a/core/java/android/view/ViewAnimationUtils.java
+++ b/core/java/android/view/ViewAnimationUtils.java
@@ -23,7 +23,7 @@
  * Defines common utilities for working with View's animations.
  *
  */
-public class ViewAnimationUtils {
+public final class ViewAnimationUtils {
     private ViewAnimationUtils() {}
     /**
      * Returns a ValueAnimator which can animate a clipping circle.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index eef09ae..45ac073 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2326,13 +2326,13 @@
      * individually during the transition.
      * @return True if the ViewGroup should be acted on together during an Activity transition.
      * The default value is false when the background is null and true when the background
-     * is not null or if {@link #getViewName()} is not null.
+     * is not null or if {@link #getTransitionName()} is not null.
      */
     public boolean isTransitionGroup() {
         if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
             return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
         } else {
-            return getBackground() != null || getViewName() != null;
+            return getBackground() != null || getTransitionName() != null;
         }
     }
 
@@ -4389,7 +4389,6 @@
             if (child.mLayerType != LAYER_TYPE_NONE) {
                 mPrivateFlags |= PFLAG_INVALIDATED;
                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
-                child.mLocalDirtyRect.union(dirty);
             }
 
             final int[] location = attachInfo.mInvalidateChildLocation;
@@ -4500,7 +4499,6 @@
 
                 if (mLayerType != LAYER_TYPE_NONE) {
                     mPrivateFlags |= PFLAG_INVALIDATED;
-                    mLocalDirtyRect.union(dirty);
                 }
 
                 return mParent;
@@ -4519,7 +4517,6 @@
 
                 if (mLayerType != LAYER_TYPE_NONE) {
                     mPrivateFlags |= PFLAG_INVALIDATED;
-                    mLocalDirtyRect.union(dirty);
                 }
 
                 return mParent;
@@ -4530,6 +4527,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,14 +4556,14 @@
      * @hide
      */
     public void damageChild(View child, final Rect dirty) {
+        if (damageChildDeferred(child)) {
+            return;
+        }
+
         ViewParent parent = this;
 
         final AttachInfo attachInfo = mAttachInfo;
         if (attachInfo != null) {
-            if (child.mLayerType != LAYER_TYPE_NONE) {
-                child.mLocalDirtyRect.union(dirty);
-            }
-
             int left = child.mLeft;
             int top = child.mTop;
             if (!child.getMatrix().isIdentity()) {
@@ -4592,9 +4611,6 @@
             if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 ||
                     dirty.intersect(0, 0, mRight - mLeft, mBottom - mTop)) {
 
-                if (mLayerType != LAYER_TYPE_NONE) {
-                    mLocalDirtyRect.union(dirty);
-                }
                 if (!getMatrix().isIdentity()) {
                     transformRect(dirty);
                 }
@@ -6913,13 +6929,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 +6947,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/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index af1de78..3f72b4c 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -52,7 +52,7 @@
      * The View whose properties are being animated by this class. This is set at
      * construction time.
      */
-    private final View mView;
+    final View mView;
 
     /**
      * The duration of the underlying Animator object. By default, we don't set the duration
@@ -253,10 +253,9 @@
     ViewPropertyAnimator(View view) {
         mView = view;
         view.ensureTransformationInfo();
-        // TODO: Disabled because of b/15287046
-        //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
-        //    mRTBackend = new ViewPropertyAnimatorRT(view);
-        //}
+        if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+            mRTBackend = new ViewPropertyAnimatorRT(view);
+        }
     }
 
     /**
@@ -434,6 +433,9 @@
         }
         mPendingAnimations.clear();
         mView.removeCallbacks(mAnimationStarter);
+        if (mRTBackend != null) {
+            mRTBackend.cancelAll();
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java
index 709efdb..8b4277a 100644
--- a/core/java/android/view/ViewPropertyAnimatorRT.java
+++ b/core/java/android/view/ViewPropertyAnimatorRT.java
@@ -50,6 +50,15 @@
         return true;
     }
 
+    public void cancelAll() {
+        for (int i = 0; i < mAnimators.length; i++) {
+            if (mAnimators[i] != null) {
+                mAnimators[i].cancel();
+                mAnimators[i] = null;
+            }
+        }
+    }
+
     private void doStartAnimation(ViewPropertyAnimator parent) {
         int size = parent.mPendingAnimations.size();
 
@@ -63,12 +72,22 @@
             NameValuesHolder holder = parent.mPendingAnimations.get(i);
             int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
 
-            RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue);
+            final float finalValue = holder.mFromValue + holder.mDeltaValue;
+            RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
             animator.setStartDelay(startDelay);
             animator.setDuration(duration);
             animator.setInterpolator(interpolator);
             animator.setTarget(mView);
             animator.start();
+
+            // Alpha is a special snowflake that has the canonical value stored
+            // in mTransformationInfo instead of in RenderNode, so we need to update
+            // it with the final value here.
+            if (property == RenderNodeAnimator.ALPHA) {
+                // Don't need null check because ViewPropertyAnimator's
+                // ctor calls ensureTransformationInfo()
+                parent.mView.mTransformationInfo.mAlpha = finalValue;
+            }
         }
 
         parent.mPendingAnimations.clear();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index aa06d15..84e30c6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -41,6 +41,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.media.AudioManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
@@ -119,6 +120,9 @@
     private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering";
     private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media";
 
+    // property used by emulator to determine display shape
+    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
+
     /**
      * Maximum time we allow the user to roll the trackball enough to generate
      * a key event, before resetting the counters.
@@ -181,7 +185,6 @@
     int mWidth;
     int mHeight;
     Rect mDirty;
-    final Rect mCurrentDirty = new Rect();
     boolean mIsAnimating;
 
     CompatibilityInfo.Translator mTranslator;
@@ -230,6 +233,7 @@
     QueuedInputEvent mPendingInputEventTail;
     int mPendingInputEventCount;
     boolean mProcessInputEventsScheduled;
+    boolean mUnbufferedInputDispatch;
     String mPendingInputEventQueueLengthCounterName = "pq";
 
     InputStage mFirstInputStage;
@@ -325,6 +329,8 @@
     /** Set to true once doDie() has been called. */
     private boolean mRemoved;
 
+    private boolean mIsEmulator;
+
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -631,27 +637,7 @@
     }
 
     void destroyHardwareLayers() {
-        if (mThread != Thread.currentThread()) {
-            if (mAttachInfo.mHardwareRenderer != null &&
-                    mAttachInfo.mHardwareRenderer.isEnabled()) {
-                HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE);
-            }
-        } else {
-            destroyHardwareLayer(mView);
-        }
-    }
-
-    private static void destroyHardwareLayer(View view) {
-        view.destroyLayer(true);
-
-        if (view instanceof ViewGroup) {
-            ViewGroup group = (ViewGroup) view;
-
-            int count = group.getChildCount();
-            for (int i = 0; i < count; i++) {
-                destroyHardwareLayer(group.getChildAt(i));
-            }
-        }
+        // TODO Implement
     }
 
     void flushHardwareLayerUpdates() {
@@ -675,12 +661,17 @@
         }
     }
 
-    public boolean invokeFunctor(long functor, boolean waitForCompletion) {
-        if (mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) {
-            return false;
-        }
-        mAttachInfo.mHardwareRenderer.invokeFunctor(functor, waitForCompletion);
-        return true;
+    /**
+     * Schedules the functor for execution in either kModeProcess or
+     * kModeProcessNoContext, depending on whether or not there is an EGLContext.
+     *
+     * @param functor The native functor to invoke
+     * @param waitForCompletion If true, this will not return until the functor
+     *                          has invoked. If false, the functor may be invoked
+     *                          asynchronously.
+     */
+    public void invokeFunctor(long functor, boolean waitForCompletion) {
+        ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
     }
 
     private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
@@ -715,23 +706,12 @@
 
             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);
                 }
 
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE;
-                mAttachInfo.mHardwareRenderer = HardwareRenderer.create(translucent);
+                mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
                 if (mAttachInfo.mHardwareRenderer != null) {
                     mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
                     mAttachInfo.mHardwareAccelerated =
@@ -871,7 +851,9 @@
 
     void invalidate() {
         mDirty.set(0, 0, mWidth, mHeight);
-        scheduleTraversals();
+        if (!mWillDrawSoon) {
+            scheduleTraversals();
+        }
     }
 
     void invalidateWorld(View view) {
@@ -1016,7 +998,9 @@
             mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
             mChoreographer.postCallback(
                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
-            scheduleConsumeBatchedInput();
+            if (!mUnbufferedInputDispatch) {
+                scheduleConsumeBatchedInput();
+            }
             notifyRendererOfFramePending();
         }
     }
@@ -1197,8 +1181,9 @@
         if ((mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0
                 && mDisplay.getDisplayId() == 0) {
             // we're fullscreen and not hosted in an ActivityView
-            isRound = mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_windowIsRound);
+            isRound = (mIsEmulator && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))
+                    || mContext.getResources().getBoolean(
+                            com.android.internal.R.bool.config_windowIsRound);
         }
         host.dispatchApplyWindowInsets(new WindowInsets(
                 mFitSystemWindowsInsets, isRound));
@@ -1527,54 +1512,55 @@
 
                         disposeResizeBuffer();
 
-                        if (mResizeBuffer == null) {
-                            mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
-                                    mWidth, mHeight);
-                        }
-                        mResizeBuffer.prepare(mWidth, mHeight, false);
-                        RenderNode layerRenderNode = mResizeBuffer.startRecording();
-                        HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight);
-                        try {
-                            final int restoreCount = layerCanvas.save();
-
-                            int yoff;
-                            final boolean scrolling = mScroller != null
-                                    && mScroller.computeScrollOffset();
-                            if (scrolling) {
-                                yoff = mScroller.getCurrY();
-                                mScroller.abortAnimation();
-                            } else {
-                                yoff = mScrollY;
-                            }
-
-                            layerCanvas.translate(0, -yoff);
-                            if (mTranslator != null) {
-                                mTranslator.translateCanvas(layerCanvas);
-                            }
-
-                            RenderNode renderNode = mView.mRenderNode;
-                            if (renderNode != null && renderNode.isValid()) {
-                                layerCanvas.drawDisplayList(renderNode, null,
-                                        RenderNode.FLAG_CLIP_CHILDREN);
-                            } else {
-                                mView.draw(layerCanvas);
-                            }
-
-                            drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
-
-                            mResizeBufferStartTime = SystemClock.uptimeMillis();
-                            mResizeBufferDuration = mView.getResources().getInteger(
-                                    com.android.internal.R.integer.config_mediumAnimTime);
-
-                            layerCanvas.restoreToCount(restoreCount);
-                            layerRenderNode.end(layerCanvas);
-                            layerRenderNode.setCaching(true);
-                            layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
-                            mTempRect.set(0, 0, mWidth, mHeight);
-                        } finally {
-                            mResizeBuffer.endRecording(mTempRect);
-                        }
-                        mAttachInfo.mHardwareRenderer.flushLayerUpdates();
+// TODO: Again....
+//                        if (mResizeBuffer == null) {
+//                            mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
+//                                    mWidth, mHeight);
+//                        }
+//                        mResizeBuffer.prepare(mWidth, mHeight, false);
+//                        RenderNode layerRenderNode = mResizeBuffer.startRecording();
+//                        HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight);
+//                        try {
+//                            final int restoreCount = layerCanvas.save();
+//
+//                            int yoff;
+//                            final boolean scrolling = mScroller != null
+//                                    && mScroller.computeScrollOffset();
+//                            if (scrolling) {
+//                                yoff = mScroller.getCurrY();
+//                                mScroller.abortAnimation();
+//                            } else {
+//                                yoff = mScrollY;
+//                            }
+//
+//                            layerCanvas.translate(0, -yoff);
+//                            if (mTranslator != null) {
+//                                mTranslator.translateCanvas(layerCanvas);
+//                            }
+//
+//                            RenderNode renderNode = mView.mRenderNode;
+//                            if (renderNode != null && renderNode.isValid()) {
+//                                layerCanvas.drawDisplayList(renderNode, null,
+//                                        RenderNode.FLAG_CLIP_CHILDREN);
+//                            } else {
+//                                mView.draw(layerCanvas);
+//                            }
+//
+//                            drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+//
+//                            mResizeBufferStartTime = SystemClock.uptimeMillis();
+//                            mResizeBufferDuration = mView.getResources().getInteger(
+//                                    com.android.internal.R.integer.config_mediumAnimTime);
+//
+//                            layerCanvas.restoreToCount(restoreCount);
+//                            layerRenderNode.end(layerCanvas);
+//                            layerRenderNode.setCaching(true);
+//                            layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
+//                            mTempRect.set(0, 0, mWidth, mHeight);
+//                        } finally {
+//                            mResizeBuffer.endRecording(mTempRect);
+//                        }
+//                        mAttachInfo.mHardwareRenderer.flushLayerUpdates();
                     }
                     mAttachInfo.mContentInsets.set(mPendingContentInsets);
                     if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -2444,12 +2430,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 +2600,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 +3882,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 +4006,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 +5279,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=");
@@ -5435,6 +5438,9 @@
                         mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
                     }
                 }
+
+                // detect emulator
+                mIsEmulator = Build.HARDWARE.contains("goldfish");
             }
         });
     }
@@ -5688,6 +5694,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 +5734,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 +5780,11 @@
 
         @Override
         public void onBatchedInputEventPending() {
-            scheduleConsumeBatchedInput();
+            if (mUnbufferedInputDispatch) {
+                super.onBatchedInputEventPending();
+            } else {
+                scheduleConsumeBatchedInput();
+            }
         }
 
         @Override
@@ -5784,6 +5805,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 +6211,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..0a44d23 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1435,7 +1435,7 @@
     /**
      * Sets the Transition that will be used for shared elements transferred into the content
      * Scene. Typical Transitions will affect size and location, such as
-     * {@link android.transition.MoveImage} and {@link android.transition.ChangeBounds}. A null
+     * {@link android.transition.ChangeBounds}. A null
      * value will cause transferred shared elements to blink to the final position.
      * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
      * @param transition The Transition to use for shared elements transferred into the content
@@ -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 9b75595..1d2f1bf 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -102,7 +102,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/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index a92bf59..bf5c84e 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -167,5 +167,5 @@
      * Invalidate all visible windows. Then report back on the callback once all windows have
      * redrawn.
      */
-    public abstract void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout);
+    public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 2b4677c..024600d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -605,21 +605,21 @@
     public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation);
 
     /**
-     * Return whether the given window should forcibly hide everything
-     * behind it.  Typically returns true for the keyguard.
+     * Return whether the given window is forcibly hiding all windows except windows with
+     * FLAG_SHOW_WHEN_LOCKED set.  Typically returns true for the keyguard.
      */
-    public boolean doesForceHide(WindowManager.LayoutParams attrs);
+    public boolean isForceHiding(WindowManager.LayoutParams attrs);
 
 
     /**
-     * Return whether the given window can become one that passes doesForceHide() test.
+     * Return whether the given window can become one that passes isForceHiding() test.
      * Typically returns true for the StatusBar.
      */
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
 
     /**
      * Determine if a window that is behind one that is force hiding
-     * (as determined by {@link #doesForceHide}) should actually be hidden.
+     * (as determined by {@link #isForceHiding}) should actually be hidden.
      * For example, typically returns false for the status bar.  Be careful
      * to return false for any window that you may hide yourself, since this
      * will conflict with what you set.
@@ -830,13 +830,11 @@
      * setting the window's frame, either here or in finishLayout().
      * 
      * @param win The window being positioned.
-     * @param attrs The LayoutParams of the window.
      * @param attached For sub-windows, the window it is attached to; this
      *                 window will already have had layoutWindow() called on it
      *                 so you can use its Rect.  Otherwise null.
      */
-    public void layoutWindowLw(WindowState win,
-            WindowManager.LayoutParams attrs, WindowState attached);
+    public void layoutWindowLw(WindowState win, WindowState attached);
 
     
     /**
@@ -1156,6 +1154,12 @@
     public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
 
     /**
+     * Show the recents task list app.
+     * @hide
+     */
+    public void showRecentApps();
+
+    /**
      * @return The current height of the input method window.
      */
     public int getInputMethodWindowVisibleHeightLw();
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..945ecf0 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -16,11 +16,16 @@
 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.util.PathParser;
 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,35 +86,60 @@
     }
 
     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)) {
-            throw new InflateException("pathInterpolator requires the controlX1 attribute");
-        } else if (!a.hasValue(com.android.internal.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);
+        this(context.getResources(), context.getTheme(), attrs);
+    }
 
-        boolean hasX2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX2);
-        boolean hasY2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY2);
-
-        if (hasX2 != hasY2) {
-            throw new InflateException(
-                    "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
-        }
-
-        if (!hasX2) {
-            initQuad(x1, y1);
+    /** @hide */
+    public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0);
         } 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);
-            initCubic(x1, y1, x2, y2);
+            a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
         }
+        parseInterpolatorFromTypeArray(a);
 
         a.recycle();
     }
 
+    private void parseInterpolatorFromTypeArray(TypedArray a) {
+        // If there is pathData defined in the xml file, then the controls points
+        // will be all coming from pathData.
+        if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+            String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+            Path path = PathParser.createPathFromPathData(pathData);
+            if (path == null) {
+                throw new InflateException("The path is null, which is created"
+                        + " from " + pathData);
+            }
+            initPath(path);
+        } else {
+            if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+                throw new InflateException("pathInterpolator requires the controlX1 attribute");
+            } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+                throw new InflateException("pathInterpolator requires the controlY1 attribute");
+            }
+            float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+            float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+
+            boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+            boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+
+            if (hasX2 != hasY2) {
+                throw new InflateException(
+                        "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+            }
+
+            if (!hasX2) {
+                initQuad(x1, y1);
+            } else {
+                float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+                float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+                initCubic(x1, y1, x2, y2);
+            }
+        }
+    }
+
     private void initQuad(float controlX, float controlY) {
         Path path = new Path();
         path.moveTo(0, 0);
@@ -199,5 +229,4 @@
         float endY = mY[endIndex];
         return startY + (fraction * (endY - startY));
     }
-
 }
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/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 66f5f6c..d3d5fff 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.Layout;
+import android.text.SpannedString;
+import android.text.TextUtils;
 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
 
 import java.util.Objects;
@@ -40,7 +42,7 @@
     /**
      * The text, tracked as a composing region.
      */
-    private final String mComposingText;
+    private final CharSequence mComposingText;
 
     /**
      * Horizontal position of the insertion marker, in the local coordinates that will be
@@ -88,7 +90,7 @@
         mSelectionStart = source.readInt();
         mSelectionEnd = source.readInt();
         mComposingTextStart = source.readInt();
-        mComposingText = source.readString();
+        mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         mInsertionMarkerHorizontal = source.readFloat();
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
@@ -109,7 +111,7 @@
         dest.writeInt(mSelectionStart);
         dest.writeInt(mSelectionEnd);
         dest.writeInt(mComposingTextStart);
-        dest.writeString(mComposingText);
+        TextUtils.writeToParcel(mComposingText, dest, flags);
         dest.writeFloat(mInsertionMarkerHorizontal);
         dest.writeFloat(mInsertionMarkerTop);
         dest.writeFloat(mInsertionMarkerBaseline);
@@ -210,12 +212,13 @@
             if (composingText == null) {
                 mComposingText = null;
             } else {
-                mComposingText = composingText.toString();
+                // Make a snapshot of the given char sequence.
+                mComposingText = new SpannedString(composingText);
             }
             return this;
         }
         private int mComposingTextStart = -1;
-        private String mComposingText = null;
+        private CharSequence mComposingText = null;
 
         /**
          * Sets the location of the text insertion point (zero width cursor) as a rectangle in
@@ -362,7 +365,7 @@
      * Returns the entire composing text.
      * @return null if there is no composition.
      */
-    public String getComposingText() {
+    public CharSequence getComposingText() {
         return mComposingText;
     }
 
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..2564ff0 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.net.WebAddress;
+import android.webkit.ValueCallback;
 
 /**
  * Manages the cookies used by an application's {@link WebView} instances.
@@ -67,13 +68,41 @@
         throw new MustOverrideException();
     }
 
-     /**
+    /**
+     * Sets whether the {@link WebView} should allow third party cookies to be set.
+     * Allowing third party cookies is a per WebView policy and can be set
+     * differently on different WebView instances.
+     * <p>
+     * Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
+     * default to allowing third party cookies. Apps targeting
+     * {@link android.os.Build.VERSION_CODES#L} or later default to disallowing
+     * third party cookies.
+     *
+     * @param webview the {@link WebView} instance to set the cookie policy on
+     * @param accept whether the {@link WebView} instance should accept
+     *               third party cookies
+     */
+    public synchronized void setAcceptThirdPartyCookies(WebView webview,
+            boolean accept) {
+        throw new MustOverrideException();
+    }
+
+    /**
+     * Gets whether the {@link WebView} should allow third party cookies to be set.
+     *
+     * @param webview the {@link WebView} instance to get the cookie policy for
+     * @return true if the {@link WebView} accepts third party cookies
+     */
+    public synchronized boolean acceptThirdPartyCookies(WebView webview) {
+        throw new MustOverrideException();
+    }
+
+    /**
      * 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 url the URL for which the cookie is to be set
      * @param value the cookie as a string, using the format of the 'Set-Cookie'
      *              HTTP response header
      */
@@ -82,6 +111,29 @@
     }
 
     /**
+     * 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
+     * will be ignored if it is expired.
+     * <p>
+     * This method is asynchronous.
+     * If a {@link ValueCallback} is provided,
+     * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+     * thread's {@link android.os.Looper} once the operation is complete.
+     * The value provided to the callback indicates whether the cookie was set successfully.
+     * You can pass {@code null} as the callback if you don't need to know when the operation
+     * completes or whether it succeeded, and in this case it is safe to call the method from a
+     * thread without a Looper.
+     *
+     * @param url the URL for which the cookie is to be set
+     * @param value the cookie as a string, using the format of the 'Set-Cookie'
+     *              HTTP response header
+     * @param callback a callback to be executed when the cookie has been set
+     */
+    public void setCookie(String url, String value, ValueCallback<Boolean> callback) {
+        throw new MustOverrideException();
+    }
+
+    /**
      * Gets the cookies for the given URL.
      *
      * @param url the URL for which the cookies are requested
@@ -121,19 +173,57 @@
     /**
      * Removes all session cookies, which are cookies without an expiration
      * date.
+     * @deprecated use {@link #removeSessionCookies(ValueCallback)} instead.
      */
     public void removeSessionCookie() {
         throw new MustOverrideException();
     }
 
     /**
-     * Removes all cookies.
+     * Removes all session cookies, which are cookies without an expiration
+     * date.
+     * <p>
+     * This method is asynchronous.
+     * If a {@link ValueCallback} is provided,
+     * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+     * thread's {@link android.os.Looper} once the operation is complete.
+     * The value provided to the callback indicates whether any cookies were removed.
+     * You can pass {@code null} as the callback if you don't need to know when the operation
+     * completes or whether any cookie were removed, and in this case it is safe to call the
+     * method from a thread without a Looper.
+     * @param callback a callback which is executed when the session cookies have been removed
      */
+    public void removeSessionCookies(ValueCallback<Boolean> callback) {
+        throw new MustOverrideException();
+    }
+
+    /**
+     * Removes all cookies.
+     * @deprecated Use {@link #removeAllCookies(ValueCallback)} instead.
+     */
+    @Deprecated
     public void removeAllCookie() {
         throw new MustOverrideException();
     }
 
     /**
+     * Removes all cookies.
+     * <p>
+     * This method is asynchronous.
+     * If a {@link ValueCallback} is provided,
+     * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+     * thread's {@link android.os.Looper} once the operation is complete.
+     * The value provided to the callback indicates whether any cookies were removed.
+     * You can pass {@code null} as the callback if you don't need to know when the operation
+     * completes or whether any cookies were removed, and in this case it is safe to call the
+     * method from a thread without a Looper.
+     * @param callback a callback which is executed when the cookies have been removed
+     */
+    public void removeAllCookies(ValueCallback<Boolean> callback) {
+        throw new MustOverrideException();
+    }
+
+    /**
      * Gets whether there are stored cookies.
      *
      * @return true if there are stored cookies
@@ -154,7 +244,9 @@
 
     /**
      * Removes all expired cookies.
+     * @deprecated The WebView handles removing expired cookies automatically.
      */
+    @Deprecated
     public void removeExpiredCookie() {
         throw new MustOverrideException();
     }
diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java
index fa760b7..231bf2d 100644
--- a/core/java/android/webkit/PermissionRequest.java
+++ b/core/java/android/webkit/PermissionRequest.java
@@ -39,6 +39,12 @@
      * Resource belongs to audio capture device, like microphone.
      */
     public final static long RESOURCE_AUDIO_CAPTURE = 1 << 2;
+    /**
+     * Resource belongs to protected media identifier.
+     * After the user grants this resource, the origin can use EME APIs to generate the license
+     * requests.
+     */
+    public final static long RESOURCE_PROTECTED_MEDIA_ID = 1 << 3;
 
     /**
      * @return the origin of web content which attempt to access the restricted
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index d630a9a..470d413 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,75 @@
     }
 
     /**
+     * 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
+     */
+    public boolean showFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
+            FileChooserParams fileChooserParams) {
+        return false;
+    }
+
+    /**
+     * Parameters used in the {@link #showFileChooser} method.
+     * This is intended to be used as a read-only data struct by the application.
+     */
+    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 +468,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..44301c3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -500,6 +500,24 @@
     }
 
     /**
+     * Sets policy for third party cookies.
+     * Developers should access this via {@link CookieManager#setShouldAcceptThirdPartyCookies}.
+     * @hide Internal API.
+     */
+    public void setAcceptThirdPartyCookies(boolean accept) {
+        throw new MustOverrideException();
+    }
+
+    /**
+     * Gets policy for third party cookies.
+     * Developers should access this via {@link CookieManager#getShouldAcceptThirdPartyCookies}.
+     * @hide Internal API
+     */
+    public boolean getAcceptThirdPartyCookies() {
+        throw new MustOverrideException();
+    }
+
+    /**
      * Sets the text size of the page. The default is {@link TextSize#NORMAL}.
      *
      * @param t the text size as a {@link TextSize} value
@@ -1460,4 +1478,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/AbsListView.java b/core/java/android/widget/AbsListView.java
index 372228c..f2692da 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -785,7 +785,7 @@
 
         setVerticalScrollBarEnabled(true);
         TypedArray a = context.obtainStyledAttributes(R.styleable.View);
-        initializeScrollbars(a);
+        initializeScrollbarsInternal(a);
         a.recycle();
     }
 
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 43f623b..7e2d809 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -16,10 +16,13 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Insets;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
@@ -31,10 +34,16 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.internal.R;
+
 public abstract class AbsSeekBar extends ProgressBar {
     private final Rect mTempRect = new Rect();
 
     private Drawable mThumb;
+    private ColorStateList mThumbTint = null;
+    private PorterDuff.Mode mThumbTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasThumbTint = false;
+
     private int mThumbOffset;
     private boolean mSplitTrack;
 
@@ -83,6 +92,15 @@
         final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
         setThumb(thumb);
 
+        if (a.hasValue(R.styleable.SeekBar_thumbTint)) {
+            mThumbTint = a.getColorStateList(R.styleable.SeekBar_thumbTint);
+            mThumbTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.SeekBar_thumbTintMode, -1), mThumbTintMode);
+            mHasThumbTint = true;
+
+            applyThumbTint();
+        }
+
         // Guess thumb offset if thumb != null, but allow layout to override.
         final int thumbOffset = a.getDimensionPixelOffset(
                 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
@@ -108,7 +126,7 @@
      * @param thumb Drawable representing the thumb
      */
     public void setThumb(Drawable thumb) {
-        boolean needUpdate;
+        final boolean needUpdate;
         // This way, calling setThumb again with the same bitmap will result in
         // it recalcuating mThumbOffset (if for example it the bounds of the
         // drawable changed)
@@ -118,6 +136,7 @@
         } else {
             needUpdate = false;
         }
+
         if (thumb != null) {
             thumb.setCallback(this);
             if (canResolveLayoutDirection()) {
@@ -136,8 +155,12 @@
                 requestLayout();
             }
         }
+
         mThumb = thumb;
+
+        applyThumbTint();
         invalidate();
+
         if (needUpdate) {
             updateThumbAndTrackPos(getWidth(), getHeight());
             if (thumb != null && thumb.isStateful()) {
@@ -160,6 +183,88 @@
     }
 
     /**
+     * Applies a tint to the thumb drawable.
+     * <p>
+     * Subsequent calls to {@link #setThumb(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#SeekBar_thumbTint
+     * @attr ref android.R.styleable#SeekBar_thumbTintMode
+     * @see Drawable#setTint(ColorStateList, PorterDuff.Mode)
+     */
+    private void setThumbTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mThumbTint = tint;
+        mThumbTintMode = tintMode;
+        mHasThumbTint = true;
+
+        applyThumbTint();
+    }
+
+    /**
+     * Applies a tint to the thumb drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setThumb(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#SeekBar_thumbTint
+     * @see #setThumbTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setThumbTint(@Nullable ColorStateList tint) {
+        setThumbTint(tint, mThumbTintMode);
+    }
+
+    /**
+     * @return the tint applied to the thumb drawable
+     * @attr ref android.R.styleable#SeekBar_thumbTint
+     * @see #setThumbTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getThumbTint() {
+        return mThumbTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setThumbTint(ColorStateList)}} to the thumb drawable. The
+     * default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#SeekBar_thumbTintMode
+     * @see #setThumbTint(ColorStateList)
+     */
+    public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setThumbTint(mThumbTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the thumb drawable
+     * @attr ref android.R.styleable#SeekBar_thumbTintMode
+     * @see #setThumbTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getThumbTintMode() {
+        return mThumbTintMode;
+    }
+
+    private void applyThumbTint() {
+        if (mThumb != null && mHasThumbTint) {
+            mThumb = mThumb.mutate();
+            mThumb.setTint(mThumbTint, mThumbTintMode);
+        }
+    }
+
+    /**
      * @see #setThumbOffset(int)
      */
     public int getThumbOffset() {
@@ -237,7 +342,10 @@
     @Override
     public void jumpDrawablesToCurrentState() {
         super.jumpDrawablesToCurrentState();
-        if (mThumb != null) mThumb.jumpToCurrentState();
+
+        if (mThumb != null) {
+            mThumb.jumpToCurrentState();
+        }
     }
 
     @Override
@@ -256,12 +364,11 @@
     }
 
     @Override
-    public void invalidateDrawable(Drawable dr) {
-        super.invalidateDrawable(dr);
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
 
-        if (dr == mThumb) {
-            // Handle changes to thumb width and height.
-            requestLayout();
+        if (mThumb != null) {
+            mThumb.setHotspot(x, y);
         }
     }
 
@@ -357,11 +464,11 @@
 
         final Drawable background = getBackground();
         if (background != null) {
-            final Rect bounds = mThumb.getBounds();
+            final Rect bounds = thumb.getBounds();
             final int offsetX = mPaddingLeft - mThumbOffset;
             final int offsetY = mPaddingTop;
-            background.setHotspotBounds(left + offsetX, bounds.top + offsetY,
-                    right + offsetX, bounds.bottom + offsetY);
+            background.setHotspotBounds(left + offsetX, top + offsetY,
+                    right + offsetX, bottom + offsetY);
         }
 
         // Canvas will be translated, so 0,0 is where we start drawing
@@ -383,8 +490,8 @@
     @Override
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-
         drawThumb(canvas);
+
     }
 
     @Override
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 3ae9508..4aa2300 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -308,6 +308,15 @@
     }
 
     @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mCheckMarkDrawable != null) {
+            mCheckMarkDrawable.setHotspot(x, y);
+        }
+    }
+
+    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setClassName(CheckedTextView.class.getName());
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 6aff4f4..9ba0fe1 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -16,9 +16,12 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
+import android.graphics.PorterDuff;
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
@@ -48,7 +51,12 @@
     private boolean mChecked;
     private int mButtonResource;
     private boolean mBroadcasting;
+
     private Drawable mButtonDrawable;
+    private ColorStateList mButtonTint = null;
+    private PorterDuff.Mode mButtonTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasButtonTint = false;
+
     private OnCheckedChangeListener mOnCheckedChangeListener;
     private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
 
@@ -74,13 +82,22 @@
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);
 
-        Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
+        final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
         if (d != null) {
             setButtonDrawable(d);
         }
 
-        boolean checked = a
-                .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
+        if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
+            mButtonTint = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
+            mButtonTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
+            mHasButtonTint = true;
+
+            applyButtonTint();
+        }
+
+        final boolean checked = a.getBoolean(
+                com.android.internal.R.styleable.CompoundButton_checked, false);
         setChecked(checked);
 
         a.recycle();
@@ -173,9 +190,11 @@
     }
 
     /**
-     * Set the background to a given Drawable, identified by its resource id.
+     * Set the button graphic to a given Drawable, identified by its resource
+     * id.
      *
-     * @param resid the resource id of the drawable to use as the background 
+     * @param resid the resource id of the drawable to use as the button
+     *        graphic
      */
     public void setButtonDrawable(int resid) {
         if (resid != 0 && resid == mButtonResource) {
@@ -192,23 +211,114 @@
     }
 
     /**
-     * Set the background to a given Drawable
+     * Set the button graphic to a given Drawable
      *
-     * @param d The Drawable to use as the background
+     * @param d The Drawable to use as the button graphic
      */
     public void setButtonDrawable(Drawable d) {
-        if (d != null) {
+        if (mButtonDrawable != d) {
             if (mButtonDrawable != null) {
                 mButtonDrawable.setCallback(null);
                 unscheduleDrawable(mButtonDrawable);
             }
-            d.setCallback(this);
-            d.setVisible(getVisibility() == VISIBLE, false);
-            mButtonDrawable = d;
-            setMinHeight(mButtonDrawable.getIntrinsicHeight());
-        }
 
-        refreshDrawableState();
+            mButtonDrawable = d;
+
+            if (d != null) {
+                d.setCallback(this);
+                d.setLayoutDirection(getLayoutDirection());
+                if (d.isStateful()) {
+                    d.setState(getDrawableState());
+                }
+                d.setVisible(getVisibility() == VISIBLE, false);
+                setMinHeight(d.getIntrinsicHeight());
+                applyButtonTint();
+            }
+        }
+    }
+
+    /**
+     * Applies a tint to the button drawable.
+     * <p>
+     * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and tint
+     * mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#CompoundButton_buttonTint
+     * @attr ref android.R.styleable#CompoundButton_buttonTintMode
+     * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    private void setButtonTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mButtonTint = tint;
+        mButtonTintMode = tintMode;
+        mHasButtonTint = true;
+
+        applyButtonTint();
+    }
+
+    /**
+     * Applies a tint to the button drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and tint
+     * mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#CompoundButton_buttonTint
+     * @see #setButtonTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    public void setButtonTint(@Nullable ColorStateList tint) {
+        setButtonTint(tint, mButtonTintMode);
+    }
+
+    /**
+     * @return the tint applied to the button drawable
+     * @attr ref android.R.styleable#CompoundButton_buttonTint
+     * @see #setButtonTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getButtonTint() {
+        return mButtonTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setButtonTint(ColorStateList)}} to the button drawable. The
+     * default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#CompoundButton_buttonTintMode
+     * @see #setButtonTint(ColorStateList)
+     */
+    public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setButtonTint(mButtonTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the button drawable
+     * @attr ref android.R.styleable#CompoundButton_buttonTintMode
+     * @see #setButtonTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getButtonTintMode() {
+        return mButtonTintMode;
+    }
+
+    private void applyButtonTint() {
+        if (mButtonDrawable != null && mHasButtonTint) {
+            mButtonDrawable = mButtonDrawable.mutate();
+            mButtonDrawable.setTint(mButtonTint, mButtonTintMode);
+        }
     }
 
     @Override
@@ -321,6 +431,15 @@
     }
 
     @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mButtonDrawable != null) {
+            mButtonDrawable.setHotspot(x, y);
+        }
+    }
+
+    @Override
     protected boolean verifyDrawable(Drawable who) {
         return super.verifyDrawable(who) || who == mButtonDrawable;
     }
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/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 2502954..90fec23 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -286,6 +286,22 @@
         mTargetDisplacement = 0.5f;
     }
 
+    /**
+     * Set the color of this edge effect in argb.
+     *
+     * @param color Color in argb
+     */
+    public void setColor(int color) {
+        mPaint.setColor(color);
+    }
+
+    /**
+     * Return the color of this edge effect in argb.
+     * @return The color of this edge effect in argb
+     */
+    public int getColor() {
+        return mPaint.getColor();
+    }
 
     /**
      * Draw into the provided canvas. Assumes that the canvas has been rotated
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4467128..13a0849 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1420,7 +1420,7 @@
                     blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
                 }
 
-                ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null,
+                ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, null,
                         0 /* no child clipping, our TextView parent enforces it */);
 
                 endOfPreviousBlock = blockEndLine;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index b029328..34f333e 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -18,9 +18,12 @@
 
 import java.util.ArrayList;
 
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
@@ -33,6 +36,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews.RemoteView;
 
+import com.android.internal.R;
+
 
 /**
  * FrameLayout is designed to block out an area on the screen to display
@@ -62,6 +67,9 @@
 
     @ViewDebug.ExportedProperty(category = "drawing")
     private Drawable mForeground;
+    private ColorStateList mForegroundTint = null;
+    private PorterDuff.Mode mForegroundTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasForegroundTint = false;
 
     @ViewDebug.ExportedProperty(category = "padding")
     private int mForegroundPaddingLeft = 0;
@@ -119,6 +127,15 @@
             setMeasureAllChildren(true);
         }
 
+        if (a.hasValue(R.styleable.FrameLayout_foregroundTint)) {
+            mForegroundTint = a.getColorStateList(R.styleable.FrameLayout_foregroundTint);
+            mForegroundTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.FrameLayout_foregroundTintMode, -1), mForegroundTintMode);
+            mHasForegroundTint = true;
+
+            applyForegroundTint();
+        }
+
         mForegroundInPadding = a.getBoolean(
                 com.android.internal.R.styleable.FrameLayout_foregroundInsidePadding, true);
 
@@ -205,6 +222,15 @@
         }
     }
 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mForeground != null) {
+            mForeground.setHotspot(x, y);
+        }
+    }
+
     /**
      * Returns a set of layout parameters with a width of
      * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
@@ -221,32 +247,34 @@
      * into account by ensuring that the children are inset to be placed
      * inside of the padding area.
      * 
-     * @param drawable The Drawable to be drawn on top of the children.
+     * @param d The Drawable to be drawn on top of the children.
      * 
      * @attr ref android.R.styleable#FrameLayout_foreground
      */
-    public void setForeground(Drawable drawable) {
-        if (mForeground != drawable) {
+    public void setForeground(Drawable d) {
+        if (mForeground != d) {
             if (mForeground != null) {
                 mForeground.setCallback(null);
                 unscheduleDrawable(mForeground);
             }
 
-            mForeground = drawable;
+            mForeground = d;
             mForegroundPaddingLeft = 0;
             mForegroundPaddingTop = 0;
             mForegroundPaddingRight = 0;
             mForegroundPaddingBottom = 0;
 
-            if (drawable != null) {
+            if (d != null) {
                 setWillNotDraw(false);
-                drawable.setCallback(this);
-                if (drawable.isStateful()) {
-                    drawable.setState(getDrawableState());
+                d.setCallback(this);
+                d.setLayoutDirection(getLayoutDirection());
+                if (d.isStateful()) {
+                    d.setState(getDrawableState());
                 }
+                applyForegroundTint();
                 if (mForegroundGravity == Gravity.FILL) {
                     Rect padding = new Rect();
-                    if (drawable.getPadding(padding)) {
+                    if (d.getPadding(padding)) {
                         mForegroundPaddingLeft = padding.left;
                         mForegroundPaddingTop = padding.top;
                         mForegroundPaddingRight = padding.right;
@@ -271,6 +299,89 @@
         return mForeground;
     }
 
+    /**
+     * Applies a tint to the foreground drawable.
+     * <p>
+     * Subsequent calls to {@link #setForeground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#FrameLayout_foregroundTint
+     * @attr ref android.R.styleable#FrameLayout_foregroundTintMode
+     * @see Drawable#setTint(ColorStateList, PorterDuff.Mode)
+     */
+    private void setForegroundTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mForegroundTint = tint;
+        mForegroundTintMode = tintMode;
+        mHasForegroundTint = true;
+
+        applyForegroundTint();
+    }
+
+    /**
+     * Applies a tint to the foreground drawable. Does not modify the current
+     * tint mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setForeground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#FrameLayout_foregroundTint
+     * @see #setForegroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setForegroundTint(@Nullable ColorStateList tint) {
+        setForegroundTint(tint, mForegroundTintMode);
+    }
+
+    /**
+     * @return the tint applied to the foreground drawable
+     * @attr ref android.R.styleable#FrameLayout_foregroundTint
+     * @see #setForegroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getForegroundTint() {
+        return mForegroundTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setForegroundTint(ColorStateList)}} to the foreground drawable.
+     * The default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#FrameLayout_foregroundTintMode
+     * @see #setForegroundTint(ColorStateList)
+     */
+    public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setForegroundTint(mForegroundTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the foreground
+     *         drawable
+     * @attr ref android.R.styleable#FrameLayout_foregroundTintMode
+     * @see #setForegroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getForegroundTintMode() {
+        return mForegroundTintMode;
+    }
+
+    private void applyForegroundTint() {
+        if (mForeground != null && mHasForegroundTint) {
+            mForeground = mForeground.mutate();
+            mForeground.setTint(mForegroundTint, mForegroundTintMode);
+        }
+    }
+
     int getPaddingLeftWithForeground() {
         return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
             mPaddingLeft + mForegroundPaddingLeft;
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/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 04b18c1..93810b3 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1326,12 +1326,28 @@
             if (sel != null) {
                positionSelector(INVALID_POSITION, sel);
                mSelectedTop = sel.getTop();
-            } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
-                View child = getChildAt(mMotionPosition - mFirstPosition);
-                if (child != null) positionSelector(mMotionPosition, child);
             } else {
-                mSelectedTop = 0;
-                mSelectorRect.setEmpty();
+                final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
+                        && mTouchMode < TOUCH_MODE_SCROLL;
+                if (inTouchMode) {
+                    // If the user's finger is down, select the motion position.
+                    final View child = getChildAt(mMotionPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mMotionPosition, child);
+                    }
+                } else if (mSelectedPosition != INVALID_POSITION) {
+                    // If we had previously positioned the selector somewhere,
+                    // put it back there. It might not match up with the data,
+                    // but it's transitioning out so it's not a big deal.
+                    final View child = getChildAt(mSelectorPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mSelectorPosition, child);
+                    }
+                } else {
+                    // Otherwise, clear selection.
+                    mSelectedTop = 0;
+                    mSelectorRect.setEmpty();
+                }
             }
 
             // Attempt to restore accessibility focus, if necessary.
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 572302a4..e97177d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -16,8 +16,10 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -44,6 +46,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews.RemoteView;
 
+import com.android.internal.R;
+
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -75,13 +79,18 @@
     private int mMaxHeight = Integer.MAX_VALUE;
 
     // these are applied to the drawable
-    private ColorFilter mColorFilter;
+    private ColorFilter mColorFilter = null;
+    private boolean mHasColorFilter = false;
     private Xfermode mXfermode;
     private int mAlpha = 255;
     private int mViewAlphaScale = 256;
     private boolean mColorMod = false;
 
     private Drawable mDrawable = null;
+    private ColorStateList mDrawableTint = null;
+    private PorterDuff.Mode mDrawableTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasDrawableTint = false;
+
     private int[] mState = null;
     private boolean mMergeState = false;
     private int mLevel = 0;
@@ -154,17 +163,21 @@
         setMaxHeight(a.getDimensionPixelSize(
                 com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
         
-        int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
+        final int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
         if (index >= 0) {
             setScaleType(sScaleTypeArray[index]);
         }
 
-        int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
-        if (tint != 0) {
-            setColorFilter(tint);
+        if (a.hasValue(R.styleable.ImageView_tint)) {
+            mDrawableTint = a.getColorStateList(R.styleable.ImageView_tint);
+            mDrawableTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.ImageView_tintMode, -1), mDrawableTintMode);
+            mHasDrawableTint = true;
+
+            applyDrawableTint();
         }
-        
-        int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255);
+
+        final int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255);
         if (alpha != 255) {
             setAlpha(alpha);
         }
@@ -435,6 +448,88 @@
     }
 
     /**
+     * Applies a tint to the image drawable.
+     * <p>
+     * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ImageView_tint
+     * @attr ref android.R.styleable#ImageView_tintMode
+     * @see Drawable#setTint(ColorStateList, PorterDuff.Mode)
+     */
+    private void setTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mDrawableTint = tint;
+        mDrawableTintMode = tintMode;
+        mHasDrawableTint = true;
+
+        applyDrawableTint();
+    }
+
+    /**
+     * Applies a tint to the image drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ImageView_tint
+     * @see Drawable#setTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setTint(@Nullable ColorStateList tint) {
+        setTint(tint, mDrawableTintMode);
+    }
+
+    /**
+     * @return the tint applied to the image drawable
+     * @attr ref android.R.styleable#ImageView_tint
+     * @see #setTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getTint() {
+        return mDrawableTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setTint(ColorStateList)}} to the image drawable. The default
+     * mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#ImageView_tintMode
+     * @see #setTint(ColorStateList)
+     */
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setTint(mDrawableTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the image drawable
+     * @attr ref android.R.styleable#ImageView_tintMode
+     * @see #setTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getTintMode() {
+        return mDrawableTintMode;
+    }
+
+    private void applyDrawableTint() {
+        if (mDrawable != null && mHasDrawableTint) {
+            mDrawable = mDrawable.mutate();
+            mDrawable.setTint(mDrawableTint, mDrawableTintMode);
+        }
+    }
+
+    /**
      * Sets a Bitmap as the content of this ImageView.
      * 
      * @param bm The bitmap to set
@@ -709,17 +804,20 @@
             mDrawable.setCallback(null);
             unscheduleDrawable(mDrawable);
         }
+
         mDrawable = d;
+
         if (d != null) {
             d.setCallback(this);
+            d.setLayoutDirection(getLayoutDirection());
             if (d.isStateful()) {
                 d.setState(getDrawableState());
             }
-            d.setLevel(mLevel);
-            d.setLayoutDirection(getLayoutDirection());
             d.setVisible(getVisibility() == VISIBLE, true);
+            d.setLevel(mLevel);
             mDrawableWidth = d.getIntrinsicWidth();
             mDrawableHeight = d.getIntrinsicHeight();
+            applyDrawableTint();
             applyColorMod();
             configureBounds();
         } else {
@@ -1010,7 +1108,30 @@
         }
     }
 
-    @Override 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mDrawable != null) {
+            mDrawable.setHotspot(x, y);
+        }
+    }
+
+    /** @hide */
+    public void animateTransform(Matrix matrix) {
+        if (matrix == null) {
+            mDrawable.setBounds(0, 0, getWidth(), getHeight());
+        } else {
+            mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
+            if (mDrawMatrix == null) {
+                mDrawMatrix = new Matrix();
+            }
+            mDrawMatrix.set(matrix);
+        }
+        invalidate();
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
@@ -1167,6 +1288,7 @@
     public void setColorFilter(ColorFilter cf) {
         if (mColorFilter != cf) {
             mColorFilter = cf;
+            mHasColorFilter = true;
             mColorMod = true;
             applyColorMod();
             invalidate();
@@ -1221,7 +1343,9 @@
         // re-applied if the Drawable is changed.
         if (mDrawable != null && mColorMod) {
             mDrawable = mDrawable.mutate();
-            mDrawable.setColorFilter(mColorFilter);
+            if (mHasColorFilter) {
+                mDrawable.setColorFilter(mColorFilter);
+            }
             mDrawable.setXfermode(mXfermode);
             mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
         }
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index eeb8015..1baeca8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1718,14 +1718,24 @@
                 }
                 mSelectedTop = sel.getTop();
             } else {
-                // If the user's finger is down, select the motion position.
-                // Otherwise, clear selection.
-                if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
+                final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
+                        || mTouchMode == TOUCH_MODE_DONE_WAITING;
+                if (inTouchMode) {
+                    // If the user's finger is down, select the motion position.
                     final View child = getChildAt(mMotionPosition - mFirstPosition);
-                    if (child != null)  {
+                    if (child != null) {
                         positionSelector(mMotionPosition, child);
                     }
+                } else if (mSelectorPosition != INVALID_POSITION) {
+                    // If we had previously positioned the selector somewhere,
+                    // put it back there. It might not match up with the data,
+                    // but it's transitioning out so it's not a big deal.
+                    final View child = getChildAt(mSelectorPosition - mFirstPosition);
+                    if (child != null) {
+                        positionSelector(mSelectorPosition, child);
+                    }
                 } else {
+                    // Otherwise, clear selection.
                     mSelectedTop = 0;
                     mSelectorRect.setEmpty();
                 }
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index b49938c..394b255 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,13 +16,17 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
+import android.graphics.PorterDuff;
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.Animatable;
@@ -210,8 +214,26 @@
     private Transformation mTransformation;
     private AlphaAnimation mAnimation;
     private boolean mHasAnimation;
+
     private Drawable mIndeterminateDrawable;
+    private ColorStateList mIndeterminateTint = null;
+    private PorterDuff.Mode mIndeterminateTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasIndeterminateTint = false;
+
     private Drawable mProgressDrawable;
+
+    private ColorStateList mProgressTint = null;
+    private PorterDuff.Mode mProgressTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasProgressTint = false;
+
+    private ColorStateList mProgressBackgroundTint = null;
+    private PorterDuff.Mode mProgressBackgroundTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasProgressBackgroundTint = false;
+
+    private ColorStateList mSecondaryProgressTint = null;
+    private PorterDuff.Mode mSecondaryProgressTintMode = PorterDuff.Mode.SRC_ATOP;
+    private boolean mHasSecondaryProgressTint = false;
+
     private Drawable mCurrentDrawable;
     Bitmap mSampleTile;
     private boolean mNoInvalidate;
@@ -257,11 +279,11 @@
         
         mNoInvalidate = true;
         
-        Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
-        if (drawable != null) {
+        final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
+        if (progressDrawable != null) {
             // Calling this method can set mMaxHeight, make sure the corresponding
             // XML attribute for mMaxHeight is read after calling this method
-            setProgressDrawableTiled(drawable);
+            setProgressDrawableTiled(progressDrawable);
         }
 
 
@@ -288,9 +310,10 @@
         setSecondaryProgress(
                 a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
 
-        drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
-        if (drawable != null) {
-            setIndeterminateDrawableTiled(drawable);
+        final Drawable indeterminateDrawable = a.getDrawable(
+                R.styleable.ProgressBar_indeterminateDrawable);
+        if (indeterminateDrawable != null) {
+            setIndeterminateDrawableTiled(indeterminateDrawable);
         }
 
         mOnlyIndeterminate = a.getBoolean(
@@ -303,6 +326,53 @@
 
         mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
 
+        if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
+            mProgressTint = a.getColorStateList(
+                    R.styleable.ProgressBar_progressTint);
+            mProgressTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.ProgressBar_progressBackgroundTintMode, -1),
+                    mProgressTintMode);
+            mHasProgressTint = true;
+
+            applyProgressLayerTint(R.id.progress, mProgressTint,
+                    mProgressTintMode, true);
+        }
+
+        if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
+            mProgressBackgroundTint = a.getColorStateList(
+                    R.styleable.ProgressBar_progressBackgroundTint);
+            mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.ProgressBar_progressTintMode, -1),
+                    mProgressBackgroundTintMode);
+            mHasProgressBackgroundTint = true;
+
+            applyProgressLayerTint(R.id.background, mProgressBackgroundTint,
+                    mProgressBackgroundTintMode, false);
+        }
+
+        if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
+            mSecondaryProgressTint = a.getColorStateList(
+                    R.styleable.ProgressBar_secondaryProgressTint);
+            mSecondaryProgressTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.ProgressBar_secondaryProgressTintMode, -1),
+                    mSecondaryProgressTintMode);
+            mHasSecondaryProgressTint = true;
+
+            applyProgressLayerTint(R.id.secondaryProgress, mSecondaryProgressTint,
+                    mSecondaryProgressTintMode, false);
+        }
+
+        if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
+            mIndeterminateTint = a.getColorStateList(
+                    R.styleable.ProgressBar_indeterminateTint);
+            mIndeterminateTintMode = Drawable.parseTintMode(a.getInt(
+                    R.styleable.ProgressBar_indeterminateTintMode, -1),
+                    mIndeterminateTintMode);
+            mHasIndeterminateTint = true;
+
+            applyIndeterminateTint();
+        }
+
         a.recycle();
 
         // If not explicitly specified this view is important for accessibility.
@@ -479,16 +549,111 @@
      * @see #setIndeterminate(boolean)
      */
     public void setIndeterminateDrawable(Drawable d) {
-        if (d != null) {
-            d.setCallback(this);
+        if (mIndeterminateDrawable != d) {
+            if (mIndeterminateDrawable != null) {
+                mIndeterminateDrawable.setCallback(null);
+                unscheduleDrawable(mIndeterminateDrawable);
+            }
+
+            mIndeterminateDrawable = d;
+
+            if (d != null) {
+                d.setCallback(this);
+                d.setLayoutDirection(getLayoutDirection());
+                if (d.isStateful()) {
+                    d.setState(getDrawableState());
+                }
+                applyIndeterminateTint();
+            }
+
+            if (mIndeterminate) {
+                mCurrentDrawable = d;
+                postInvalidate();
+            }
         }
-        mIndeterminateDrawable = d;
-        if (mIndeterminateDrawable != null && canResolveLayoutDirection()) {
-            mIndeterminateDrawable.setLayoutDirection(getLayoutDirection());
-        }
-        if (mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
+    }
+
+    /**
+     * Applies a tint to the indeterminate drawable.
+     * <p>
+     * Subsequent calls to {@link #setVisibilminateDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and
+     * tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
+     * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    private void setIndeterminateTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mIndeterminateTint = tint;
+        mIndeterminateTintMode = tintMode;
+        mHasIndeterminateTint = true;
+
+        applyIndeterminateTint();
+    }
+
+    /**
+     * Applies a tint to the indeterminate drawable. Does not modify the
+     * current tint mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and
+     * tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
+     * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setIndeterminateTint(@Nullable ColorStateList tint) {
+        setIndeterminateTint(tint, mIndeterminateTintMode);
+    }
+
+    /**
+     * @return the tint applied to the indeterminate drawable
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
+     * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getIndeterminateTint() {
+        return mIndeterminateTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setIndeterminateTint(ColorStateList)} to the indeterminate
+     * drawable. The default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
+     * @see #setIndeterminateTint(ColorStateList)
+     */
+    public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setIndeterminateTint(mIndeterminateTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the indeterminate drawable
+     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
+     * @see #setIndeterminateTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getIndeterminateTintMode() {
+        return mIndeterminateTintMode;
+    }
+
+    private void applyIndeterminateTint() {
+        if (mIndeterminateDrawable != null && mHasIndeterminateTint) {
+            mIndeterminateDrawable = mIndeterminateDrawable.mutate();
+            mIndeterminateDrawable.setTint(mIndeterminateTint, mIndeterminateTintMode);
         }
     }
 
@@ -532,42 +697,340 @@
      * @see #setIndeterminate(boolean)
      */
     public void setProgressDrawable(Drawable d) {
-        boolean needUpdate;
-        if (mProgressDrawable != null && d != mProgressDrawable) {
-            mProgressDrawable.setCallback(null);
-            needUpdate = true;
-        } else {
-            needUpdate = false;
-        }
+        if (mProgressDrawable != d) {
+            if (mProgressDrawable != null) {
+                mProgressDrawable.setCallback(null);
+                unscheduleDrawable(mProgressDrawable);
+            }
 
-        if (d != null) {
-            d.setCallback(this);
-            if (canResolveLayoutDirection()) {
+            mProgressDrawable = d;
+
+            if (d != null) {
+                d.setCallback(this);
                 d.setLayoutDirection(getLayoutDirection());
+                if (d.isStateful()) {
+                    d.setState(getDrawableState());
+                }
+
+                // Make sure the ProgressBar is always tall enough
+                int drawableHeight = d.getMinimumHeight();
+                if (mMaxHeight < drawableHeight) {
+                    mMaxHeight = drawableHeight;
+                    requestLayout();
+                }
+
+                if (mHasProgressTint) {
+                    applyProgressLayerTint(R.id.progress, mProgressTint, mProgressTintMode, true);
+                }
+
+                if (mHasProgressBackgroundTint) {
+                    applyProgressLayerTint(R.id.background, mProgressBackgroundTint,
+                            mProgressBackgroundTintMode, false);
+                }
+
+                if (mHasSecondaryProgressTint) {
+                    applyProgressLayerTint(R.id.secondaryProgress, mSecondaryProgressTint,
+                            mSecondaryProgressTintMode, false);
+                }
             }
 
-            // Make sure the ProgressBar is always tall enough
-            int drawableHeight = d.getMinimumHeight();
-            if (mMaxHeight < drawableHeight) {
-                mMaxHeight = drawableHeight;
-                requestLayout();
+            if (!mIndeterminate) {
+                mCurrentDrawable = d;
+                postInvalidate();
             }
-        }
-        mProgressDrawable = d;
-        if (!mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
-        }
 
-        if (needUpdate) {
             updateDrawableBounds(getWidth(), getHeight());
             updateDrawableState();
+
             doRefreshProgress(R.id.progress, mProgress, false, false);
             doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
         }
     }
 
     /**
+     * Applies a tint to the progress indicator, if one exists, or to the
+     * entire progress drawable otherwise.
+     * <p>
+     * The progress indicator should be specified as a layer with
+     * id {@link android.R.id#progress} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and
+     * tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_progressTint
+     * @attr ref android.R.styleable#ProgressBar_progressTintMode
+     * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    private void setProgressTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mProgressTint = tint;
+        mProgressTintMode = tintMode;
+        mHasProgressTint = true;
+
+        applyProgressLayerTint(R.id.progress, tint, tintMode, true);
+    }
+
+    /**
+     * Applies a tint to the progress indicator, if one exists, or to the
+     * entire progress drawable otherwise. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * The progress indicator should be specified as a layer with
+     * id {@link android.R.id#progress} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
+     * automatically mutate the drawable and apply the specified tint and
+     * tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_progressTint
+     * @see #setProgressTint(ColorStateList)
+     */
+    public void setProgressTint(@Nullable ColorStateList tint) {
+        setProgressTint(tint, mProgressTintMode);
+    }
+
+    /**
+     * @return the tint applied to the progress drawable
+     * @attr ref android.R.styleable#ProgressBar_progressTint
+     * @see #setProgressTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getProgressTint() {
+        return mProgressTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setProgressTint(ColorStateList)}} to the progress
+     * indicator. The default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#ProgressBar_progressTintMode
+     * @see #setProgressTint(ColorStateList)
+     */
+    public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setProgressTint(mProgressTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the progress drawable
+     * @attr ref android.R.styleable#ProgressBar_progressTintMode
+     * @see #setProgressTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getProgressTintMode() {
+        return mProgressTintMode;
+    }
+
+    /**
+     * Applies a tint to the progress background, if one exists.
+     * <p>
+     * The progress background must be specified as a layer with
+     * id {@link android.R.id#background} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
+     * drawable contains a progress background will automatically mutate the
+     * drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
+     * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    private void setProgressBackgroundTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mProgressBackgroundTint = tint;
+        mProgressBackgroundTintMode = tintMode;
+        mHasProgressBackgroundTint = true;
+
+        applyProgressLayerTint(R.id.background, tint, tintMode, false);
+    }
+
+    /**
+     * Applies a tint to the progress background, if one exists. Does not
+     * modify the current tint mode, which is
+     * {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * The progress background must be specified as a layer with
+     * id {@link android.R.id#background} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
+     * drawable contains a progress background will automatically mutate the
+     * drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
+     * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setProgressBackgroundTint(@Nullable ColorStateList tint) {
+        setProgressBackgroundTint(tint, mProgressBackgroundTintMode);
+    }
+
+    /**
+     * @return the tint applied to the progress background
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
+     * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getProgressBackgroundTint() {
+        return mProgressBackgroundTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setProgressBackgroundTint(ColorStateList)}} to the progress
+     * background. The default mode is {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
+     * @see #setProgressBackgroundTint(ColorStateList)
+     */
+    public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setProgressBackgroundTint(mProgressBackgroundTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the progress
+     *         background
+     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
+     * @see #setProgressBackgroundTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getProgressBackgroundTintMode() {
+        return mProgressBackgroundTintMode;
+    }
+
+    /**
+     * Applies a tint to the secondary progress indicator, if one exists.
+     * <p>
+     * The secondary progress indicator must be specified as a layer with
+     * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
+     * drawable contains a secondary progress indicator will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
+     * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
+     */
+    private void setSecondaryProgressTint(@Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode) {
+        mSecondaryProgressTint = tint;
+        mSecondaryProgressTintMode = tintMode;
+        mHasSecondaryProgressTint = true;
+
+        applyProgressLayerTint(R.id.secondaryProgress, tint, tintMode, false);
+    }
+
+    /**
+     * Applies a tint to the secondary progress indicator, if one exists.
+     * Does not modify the current tint mode, which is
+     * {@link PorterDuff.Mode#SRC_ATOP} by default.
+     * <p>
+     * The secondary progress indicator must be specified as a layer with
+     * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
+     * used as the progress drawable.
+     * <p>
+     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
+     * drawable contains a secondary progress indicator will automatically
+     * mutate the drawable and apply the specified tint and tint mode using
+     * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
+     * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode)
+     */
+    public void setSecondaryProgressTint(@Nullable ColorStateList tint) {
+        setSecondaryProgressTint(tint, mSecondaryProgressTintMode);
+    }
+
+    /**
+     * @return the tint applied to the secondary progress drawable
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
+     * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public ColorStateList getSecondaryProgressTint() {
+        return mSecondaryProgressTint;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setSecondaryProgressTint(ColorStateList)}} to the secondary
+     * progress indicator. The default mode is
+     * {@link PorterDuff.Mode#SRC_ATOP}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
+     * @see #setSecondaryProgressTint(ColorStateList)
+     */
+    public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
+        setSecondaryProgressTint(mSecondaryProgressTint, tintMode);
+    }
+
+    /**
+     * @return the blending mode used to apply the tint to the secondary
+     *         progress drawable
+     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
+     * @see #setSecondaryProgressTint(ColorStateList, PorterDuff.Mode)
+     */
+    @Nullable
+    public PorterDuff.Mode getSecondaryProgressTintMode() {
+        return mSecondaryProgressTintMode;
+    }
+
+    private void applyProgressLayerTint(int layerId, @Nullable ColorStateList tint,
+            @Nullable PorterDuff.Mode tintMode, boolean shouldFallback) {
+        final Drawable d = mProgressDrawable;
+        if (d != null) {
+            mProgressDrawable = d.mutate();
+
+            Drawable layer = null;
+            if (d instanceof LayerDrawable) {
+                layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
+            }
+
+            if (shouldFallback && layer == null) {
+                layer = d;
+            }
+
+            if (layer != null) {
+                layer.setTint(tint, tintMode);
+            }
+        }
+    }
+
+    /**
      * Define the tileable drawable used to draw the progress bar in
      * progress mode.
      * <p>
@@ -670,6 +1133,22 @@
         }
     }
 
+    private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) {
+        Drawable layer = null;
+
+        // We expect a layer drawable, so try to find the target ID.
+        final Drawable d = mCurrentDrawable;
+        if (d instanceof LayerDrawable) {
+            layer = ((LayerDrawable) d).findDrawableByLayerId(id);
+        }
+
+        if (fallback && layer == null) {
+            layer = d;
+        }
+
+        layer.mutate().setTint(tint, tintMode);
+    }
+
     private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
             boolean callBackToApp) {
         float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
@@ -1144,6 +1623,19 @@
         }
     }
 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mProgressDrawable != null) {
+            mProgressDrawable.setHotspot(x, y);
+        }
+
+        if (mIndeterminateDrawable != null) {
+            mIndeterminateDrawable.setHotspot(x, y);
+        }
+    }
+
     static class SavedState extends BaseSavedState {
         int progress;
         int secondaryProgress;
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 0c31496..23fa402 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -112,6 +112,15 @@
         }
     }
 
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mOverlay != null) {
+            mOverlay.setHotspot(x, y);
+        }
+    }
+
     /** This call has no effect anymore, as there is only one QuickContact mode */
     @SuppressWarnings("unused")
     public void setMode(int size) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index f7d20b53..82637a1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1781,7 +1781,9 @@
         Parcel p = Parcel.obtain();
         writeToParcel(p, 0);
         p.setDataPosition(0);
-        return new RemoteViews(p);
+        RemoteViews rv = new RemoteViews(p);
+        p.recycle();
+        return rv;
     }
 
     public String getPackage() {
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 9601d4a..9914800 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -25,6 +25,7 @@
 import android.database.DataSetObserver;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -427,9 +428,15 @@
      * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
      * returned from {@link #getAdapter()} will always return 0. Calling
      * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
-     * 1.
+     * 1. On API {@link Build.VERSION_CODES#L} and above, attempting to set an
+     * adapter with more than one view type will throw an
+     * {@link IllegalArgumentException}.
+     *
+     * @param adapter the adapter to set
      *
      * @see AbsSpinner#setAdapter(SpinnerAdapter)
+     * @throws IllegalArgumentException if the adapter has more than one view
+     *         type
      */
     @Override
     public void setAdapter(SpinnerAdapter adapter) {
@@ -437,6 +444,12 @@
 
         mRecycler.clear();
 
+        final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion >= Build.VERSION_CODES.L
+                && adapter != null && adapter.getViewTypeCount() != 1) {
+            throw new IllegalArgumentException("Spinner adapter view type count must be 1");
+        }
+
         if (mPopup != null) {
             mPopup.setAdapter(new DropDownAdapter(adapter));
         } else {
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index c5c6e64..9a8380d 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -90,6 +90,7 @@
     private boolean mSplitTrack;
     private CharSequence mTextOn;
     private CharSequence mTextOff;
+    private boolean mShowText;
 
     private int mTouchMode;
     private int mTouchSlop;
@@ -188,6 +189,7 @@
         mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
         mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
         mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
+        mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
         mThumbTextPadding = a.getDimensionPixelSize(
                 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
         mSwitchMinWidth = a.getDimensionPixelSize(
@@ -533,20 +535,43 @@
         requestLayout();
     }
 
+    /**
+     * Sets whether the on/off text should be displayed.
+     *
+     * @param showText {@code true} to display on/off text
+     * @hide
+     */
+    public void setShowText(boolean showText) {
+        if (mShowText != showText) {
+            mShowText = showText;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return whether the on/off text should be displayed
+     * @hide
+     */
+    public boolean getShowText() {
+        return mShowText;
+    }
+
     @Override
     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mOnLayout == null) {
-            mOnLayout = makeLayout(mTextOn);
-        }
+        if (mShowText) {
+            if (mOnLayout == null) {
+                mOnLayout = makeLayout(mTextOn);
+            }
 
-        if (mOffLayout == null) {
-            mOffLayout = makeLayout(mTextOff);
+            if (mOffLayout == null) {
+                mOffLayout = makeLayout(mTextOff);
+            }
         }
 
         mTrackDrawable.getPadding(mTempRect);
 
-        final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
-                + mThumbTextPadding * 2;
+        final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+                + mThumbTextPadding * 2 : 0;
         mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
 
         final int switchWidth = Math.max(mSwitchMinWidth,
@@ -568,9 +593,10 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(event);
-        Layout layout =  isChecked() ? mOnLayout : mOffLayout;
-        if (layout != null && !TextUtils.isEmpty(layout.getText())) {
-            event.getText().add(layout.getText());
+
+        final CharSequence text = isChecked() ? mTextOn : mTextOff;
+        if (text != null) {
+            event.getText().add(text);
         }
     }
 
@@ -963,6 +989,19 @@
     }
 
     @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mThumbDrawable != null) {
+            mThumbDrawable.setHotspot(x, y);
+        }
+
+        if (mTrackDrawable != null) {
+            mTrackDrawable.setHotspot(x, y);
+        }
+    }
+
+    @Override
     public void invalidateDrawable(Drawable drawable) {
         super.invalidateDrawable(drawable);
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 84202eb..8567bed 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2551,7 +2551,7 @@
             r = c.getResources();
 
         setRawTextSize(TypedValue.applyDimension(
-            unit, size, r.getDisplayMetrics()));
+                unit, size, r.getDisplayMetrics()));
     }
 
     private void setRawTextSize(float size) {
@@ -2727,7 +2727,6 @@
     /**
      * Sets whether the soft input method will be made visible when this
      * TextView gets focused. The default is true.
-     * @hide
      */
     @android.view.RemotableViewMethod
     public final void setShowSoftInputOnFocus(boolean show) {
@@ -2738,7 +2737,6 @@
     /**
      * Returns whether the soft input method will be made visible when this
      * TextView gets focused. The default is true.
-     * @hide
      */
     public final boolean getShowSoftInputOnFocus() {
         // When there is no Editor, return default true value
@@ -3506,6 +3504,33 @@
     }
 
     @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            if (dr.mDrawableTop != null) {
+                dr.mDrawableTop.setHotspot(x, y);
+            }
+            if (dr.mDrawableBottom != null) {
+                dr.mDrawableBottom.setHotspot(x, y);
+            }
+            if (dr.mDrawableLeft != null) {
+                dr.mDrawableLeft.setHotspot(x, y);
+            }
+            if (dr.mDrawableRight != null) {
+                dr.mDrawableRight.setHotspot(x, y);
+            }
+            if (dr.mDrawableStart != null) {
+                dr.mDrawableStart.setHotspot(x, y);
+            }
+            if (dr.mDrawableEnd != null) {
+                dr.mDrawableEnd.setHotspot(x, y);
+            }
+        }
+    }
+
+    @Override
     public Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
 
@@ -7967,43 +7992,39 @@
     }
 
     /**
-     * Returns the TextView_textColor attribute from the
-     * TypedArray, if set, or the TextAppearance_textColor
-     * from the TextView_textAppearance attribute, if TextView_textColor
-     * was not set directly.
+     * Returns the TextView_textColor attribute from the TypedArray, if set, or
+     * the TextAppearance_textColor from the TextView_textAppearance attribute,
+     * if TextView_textColor was not set directly.
      */
     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
-        ColorStateList colors;
-        colors = attrs.getColorStateList(com.android.internal.R.styleable.
-                                         TextView_textColor);
-
+        // It's not safe to use this method from apps. The parameter 'attrs'
+        // must have been obtained using the TextView filter array which is not
+        // available to the SDK. As such, we grab a default TypedArray with the
+        // right filter instead here.
+        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
+        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
         if (colors == null) {
-            int ap = attrs.getResourceId(com.android.internal.R.styleable.
-                                         TextView_textAppearance, -1);
-            if (ap != -1) {
-                TypedArray appearance;
-                appearance = context.obtainStyledAttributes(ap,
-                                            com.android.internal.R.styleable.TextAppearance);
-                colors = appearance.getColorStateList(com.android.internal.R.styleable.
-                                                  TextAppearance_textColor);
+            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
+            if (ap != 0) {
+                final TypedArray appearance = context.obtainStyledAttributes(
+                        ap, R.styleable.TextAppearance);
+                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
                 appearance.recycle();
             }
         }
+        a.recycle();
 
         return colors;
     }
 
     /**
-     * Returns the default color from the TextView_textColor attribute
-     * from the AttributeSet, if set, or the default color from the
-     * TextAppearance_textColor from the TextView_textAppearance attribute,
-     * if TextView_textColor was not set directly.
+     * Returns the default color from the TextView_textColor attribute from the
+     * AttributeSet, if set, or the default color from the
+     * TextAppearance_textColor from the TextView_textAppearance attribute, if
+     * TextView_textColor was not set directly.
      */
-    public static int getTextColor(Context context,
-                                   TypedArray attrs,
-                                   int def) {
-        ColorStateList colors = getTextColors(context, attrs);
-
+    public static int getTextColor(Context context, TypedArray attrs, int def) {
+        final ColorStateList colors = getTextColors(context, attrs);
         if (colors == null) {
             return def;
         } else {
@@ -9034,11 +9055,11 @@
         }
 
         public void drawTextRun(Canvas c, int start, int end,
-                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
-                    contextCount, x, y, flags, p);
+                    contextCount, x, y, isRtl, p);
         }
 
         public float measureText(int start, int end, Paint p) {
@@ -9050,20 +9071,20 @@
         }
 
         public float getTextRunAdvances(int start, int end, int contextStart,
-                int contextEnd, int flags, float[] advances, int advancesIndex,
+                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
                 Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             return p.getTextRunAdvances(mChars, start + mStart, count,
-                    contextStart + mStart, contextCount, flags, advances,
+                    contextStart + mStart, contextCount, isRtl, advances,
                     advancesIndex);
         }
 
-        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
                 int offset, int cursorOpt, Paint p) {
             int contextCount = contextEnd - contextStart;
             return p.getTextRunCursor(mChars, contextStart + mStart,
-                    contextCount, flags, offset + mStart, cursorOpt);
+                    contextCount, dir, offset + mStart, cursorOpt);
         }
     }
 
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 4f1cd68..122df2c 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -124,6 +124,9 @@
     private CharSequence mTitleText;
     private CharSequence mSubtitleText;
 
+    private int mTitleTextColor;
+    private int mSubtitleTextColor;
+
     // Clear me after use.
     private final ArrayList<View> mTempViews = new ArrayList<View>();
 
@@ -498,7 +501,12 @@
                 mTitleTextView = new TextView(context);
                 mTitleTextView.setSingleLine();
                 mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
-                mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
+                if (mTitleTextAppearance != 0) {
+                    mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
+                }
+                if (mTitleTextColor != 0) {
+                    mTitleTextView.setTextColor(mTitleTextColor);
+                }
             }
             if (mTitleTextView.getParent() == null) {
                 addSystemView(mTitleTextView);
@@ -546,7 +554,12 @@
                 mSubtitleTextView = new TextView(context);
                 mSubtitleTextView.setSingleLine();
                 mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END);
-                mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
+                if (mSubtitleTextAppearance != 0) {
+                    mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
+                }
+                if (mSubtitleTextColor != 0) {
+                    mSubtitleTextView.setTextColor(mSubtitleTextColor);
+                }
             }
             if (mSubtitleTextView.getParent() == null) {
                 addSystemView(mSubtitleTextView);
@@ -583,6 +596,30 @@
     }
 
     /**
+     * Sets the text color of the title, if present.
+     *
+     * @param color The new text color in 0xAARRGGBB format
+     */
+    public void setTitleTextColor(int color) {
+        mTitleTextColor = color;
+        if (mTitleTextView != null) {
+            mTitleTextView.setTextColor(color);
+        }
+    }
+
+    /**
+     * Sets the text color of the subtitle, if present.
+     *
+     * @param color The new text color in 0xAARRGGBB format
+     */
+    public void setSubtitleTextColor(int color) {
+        mSubtitleTextColor = color;
+        if (mSubtitleTextView != null) {
+            mSubtitleTextView.setTextColor(color);
+        }
+    }
+
+    /**
      * Set the icon to use for the toolbar's navigation button.
      *
      * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
@@ -598,6 +635,17 @@
     }
 
     /**
+     * Retrieve the currently configured content description for the navigation button view.
+     * This will be used to describe the navigation action to users through mechanisms such
+     * as screen readers or tooltips.
+     *
+     * @return The navigation button's content description
+     */
+    public CharSequence getNavigationContentDescription() {
+        return mNavButtonView != null ? mNavButtonView.getContentDescription() : null;
+    }
+
+    /**
      * Set a content description for the navigation button if one is present. The content
      * description will be read via screen readers or other accessibility systems to explain
      * the action of the navigation button.
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 2b62552..8ee0a1b 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -22,6 +22,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.media.AudioManager;
+import android.media.ClosedCaptionRenderer;
 import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
@@ -316,6 +317,7 @@
                     context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
             controller.registerRenderer(new WebVttRenderer(context));
             controller.registerRenderer(new TtmlRenderer(context));
+            controller.registerRenderer(new ClosedCaptionRenderer(context));
             mMediaPlayer.setSubtitleAnchor(controller, this);
 
             if (mAudioSession != 0) {
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 0769b08..7a9137b 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -76,6 +76,8 @@
     void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs);
     void noteWifiStopped(in WorkSource ws);
     void noteWifiState(int wifiState, String accessPoint);
+    void noteWifiSupplicantStateChanged(int supplState, boolean failedAuth);
+    void noteWifiRssiChanged(int newRssi);
     void noteBluetoothOn();
     void noteBluetoothOff();
     void noteBluetoothState(int bluetoothState);
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/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 7e11850..4995ea1 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -28,7 +28,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
-import android.webkit.WebViewFactory;
 
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -55,6 +54,11 @@
     // that is done.
     public static long COMMIT_PERIOD = 3*60*60*1000;  // Commit current stats every 3 hours
 
+    // Minimum uptime period before committing.  If the COMMIT_PERIOD has elapsed but
+    // the total uptime has not exceeded this amount, then the commit will be held until
+    // it is reached.
+    public static long COMMIT_UPTIME_PERIOD = 60*60*1000;  // Must have at least 1 hour elapsed
+
     public static final int STATE_NOTHING = -1;
     public static final int STATE_PERSISTENT = 0;
     public static final int STATE_TOP = 1;
@@ -81,6 +85,24 @@
     public static final int PSS_USS_MAXIMUM = 6;
     public static final int PSS_COUNT = PSS_USS_MAXIMUM+1;
 
+    public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0;
+    public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1;
+    public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2;
+    public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3;
+    public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4;
+    public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5;
+    public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6;
+    public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7;
+    public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8;
+    public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9;
+    public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10;
+    public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11;
+    public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12;
+    public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13;
+    public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14;
+    public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15;
+    public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1;
+
     public static final int ADJ_NOTHING = -1;
     public static final int ADJ_MEM_FACTOR_NORMAL = 0;
     public static final int ADJ_MEM_FACTOR_MODERATE = 1;
@@ -174,7 +196,7 @@
     static final String CSV_SEP = "\t";
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 14;
+    private static final int PARCEL_VERSION = 18;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535453;
 
@@ -200,11 +222,16 @@
     public int mMemFactor = STATE_NOTHING;
     public long mStartTime;
 
+    public int[] mSysMemUsageTable = null;
+    public int mSysMemUsageTableSize = 0;
+    public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
+
     public long mTimePeriodStartClock;
     public long mTimePeriodStartRealtime;
     public long mTimePeriodEndRealtime;
+    public long mTimePeriodStartUptime;
+    public long mTimePeriodEndUptime;
     String mRuntime;
-    String mWebView;
     boolean mRunning;
 
     static final int LONGS_SIZE = 4096;
@@ -304,11 +331,77 @@
             mMemFactorDurations[i] += other.mMemFactorDurations[i];
         }
 
+        for (int i=0; i<other.mSysMemUsageTableSize; i++) {
+            int ent = other.mSysMemUsageTable[i];
+            int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+            long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK));
+        }
+
         if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
             mTimePeriodStartClock = other.mTimePeriodStartClock;
             mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
         }
         mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
+        mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+    }
+
+    public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        if (mMemFactor != STATE_NOTHING) {
+            int state = mMemFactor * STATE_COUNT;
+            mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1;
+            for (int i=0; i<3; i++) {
+                mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem;
+            }
+            addSysMemUsage(state, mSysMemUsageArgs, 0);
+        }
+    }
+
+    void addSysMemUsage(int state, long[] data, int dataOff) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        int off;
+        if (idx >= 0) {
+            off = mSysMemUsageTable[idx];
+        } else {
+            mAddLongTable = mSysMemUsageTable;
+            mAddLongTableSize = mSysMemUsageTableSize;
+            off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT);
+            mSysMemUsageTable = mAddLongTable;
+            mSysMemUsageTableSize = mAddLongTableSize;
+        }
+        long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+        idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK;
+        addSysMemUsage(longs, idx, data, dataOff);
+    }
+
+    static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) {
+        final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        if (dstCount == 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) {
+                dstData[dstOff+i] = addData[addOff+i];
+            }
+        } else if (addCount > 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) {
+                if (dstData[dstOff+i] > addData[addOff+i]) {
+                    dstData[dstOff+i] = addData[addOff+i];
+                }
+                dstData[dstOff+i+1] = (long)(
+                        ((dstData[dstOff+i+1]*(double)dstCount)
+                                + (addData[addOff+i+1]*(double)addCount))
+                                / (dstCount+addCount) );
+                if (dstData[dstOff+i+2] < addData[addOff+i+2]) {
+                    dstData[dstOff+i+2] = addData[addOff+i+2];
+                }
+            }
+        }
     }
 
     public static final Parcelable.Creator<ProcessStats> CREATOR
@@ -564,6 +657,164 @@
         return totalTime;
     }
 
+    static class PssAggr {
+        long pss = 0;
+        long samples = 0;
+
+        void add(long newPss, long newSamples) {
+            pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
+                    / (samples+newSamples);
+            samples += newSamples;
+        }
+    }
+
+    public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) {
+        data.totalTime = 0;
+        for (int i=0; i<STATE_COUNT; i++) {
+            data.processStateWeight[i] = 0;
+            data.processStatePss[i] = 0;
+            data.processStateTime[i] = 0;
+            data.processStateSamples[i] = 0;
+        }
+        for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) {
+            data.sysMemUsage[i] = 0;
+        }
+        data.sysMemCachedWeight = 0;
+        data.sysMemFreeWeight = 0;
+        data.sysMemZRamWeight = 0;
+        data.sysMemKernelWeight = 0;
+        data.sysMemNativeWeight = 0;
+        data.sysMemSamples = 0;
+        long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            int ent = mSysMemUsageTable[i];
+            long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+            addSysMemUsage(totalMemUsage, 0, longs, idx);
+        }
+        for (int is=0; is<data.screenStates.length; is++) {
+            for (int im=0; im<data.memStates.length; im++) {
+                int memBucket = data.screenStates[is] + data.memStates[im];
+                int stateBucket = memBucket * STATE_COUNT;
+                long memTime = mMemFactorDurations[memBucket];
+                if (mMemFactor == memBucket) {
+                    memTime += now - mStartTime;
+                }
+                data.totalTime += memTime;
+                int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket);
+                long[] longs = totalMemUsage;
+                int idx = 0;
+                if (sysIdx >= 0) {
+                    int ent = mSysMemUsageTable[sysIdx];
+                    long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+                    int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+                    if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) {
+                        addSysMemUsage(data.sysMemUsage, 0, longs, idx);
+                        longs = tmpLongs;
+                        idx = tmpIdx;
+                    }
+                }
+                data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE]
+                        * (double)memTime;
+                data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
+                        * (double)memTime;
+                data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
+                        * (double)memTime;
+                data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
+             }
+        }
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int iproc=0; iproc<procMap.size(); iproc++) {
+            SparseArray<ProcessState> uids = procMap.valueAt(iproc);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final ProcessState proc = uids.valueAt(iu);
+                final PssAggr fgPss = new PssAggr();
+                final PssAggr bgPss = new PssAggr();
+                final PssAggr cachedPss = new PssAggr();
+                boolean havePss = false;
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    int off = proc.mDurationsTable[i];
+                    int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    int procState = type % STATE_COUNT;
+                    long samples = proc.getPssSampleCount(type);
+                    if (samples > 0) {
+                        long avg = proc.getPssAverage(type);
+                        havePss = true;
+                        if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                            fgPss.add(avg, samples);
+                        } else if (procState <= STATE_RECEIVER) {
+                            bgPss.add(avg, samples);
+                        } else {
+                            cachedPss.add(avg, samples);
+                        }
+                    }
+                }
+                if (!havePss) {
+                    continue;
+                }
+                boolean fgHasBg = false;
+                boolean fgHasCached = false;
+                boolean bgHasCached = false;
+                if (fgPss.samples < 3 && bgPss.samples > 0) {
+                    fgHasBg = true;
+                    fgPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (fgPss.samples < 3 && cachedPss.samples > 0) {
+                    fgHasCached = true;
+                    fgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && cachedPss.samples > 0) {
+                    bgHasCached = true;
+                    bgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
+                    bgPss.add(fgPss.pss, fgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
+                    cachedPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
+                    cachedPss.add(fgPss.pss, fgPss.samples);
+                }
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    final int off = proc.mDurationsTable[i];
+                    final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    long time = getLong(off, 0);
+                    if (proc.mCurState == type) {
+                        time += now - proc.mStartTime;
+                    }
+                    final int procState = type % STATE_COUNT;
+                    data.processStateTime[procState] += time;
+                    long samples = proc.getPssSampleCount(type);
+                    long avg;
+                    if (samples > 0) {
+                        avg = proc.getPssAverage(type);
+                    } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                        samples = fgPss.samples;
+                        avg = fgPss.pss;
+                    } else if (procState <= STATE_RECEIVER) {
+                        samples = bgPss.samples;
+                        avg = bgPss.pss;
+                    } else {
+                        samples = cachedPss.samples;
+                        avg = cachedPss.pss;
+                    }
+                    double newAvg = ( (data.processStatePss[procState]
+                            * (double)data.processStateSamples[procState])
+                                + (avg*(double)samples)
+                            ) / (data.processStateSamples[procState]+samples);
+                    data.processStatePss[procState] = (long)newAvg;
+                    data.processStateSamples[procState] += samples;
+                    data.processStateWeight[procState] += avg * (double)time;
+                }
+            }
+        }
+    }
+
     static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc,
             int[] screenStates, int[] memStates, int[] procStates, long now) {
         long totalTime = 0;
@@ -679,6 +930,62 @@
         }
     }
 
+    long getSysMemUsageValue(int state, int index) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0;
+    }
+
+    void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label,
+            int bucket, int index) {
+        pw.print(prefix); pw.print(label);
+        pw.print(": ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+        pw.print(" min, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+        pw.print(" avg, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+        pw.println(" max");
+    }
+
+    void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates,
+            int[] memStates) {
+        int printedScreen = -1;
+        for (int is=0; is<screenStates.length; is++) {
+            int printedMem = -1;
+            for (int im=0; im<memStates.length; im++) {
+                final int iscreen = screenStates[is];
+                final int imem = memStates[im];
+                final int bucket = ((iscreen + imem) * STATE_COUNT);
+                long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT);
+                if (count > 0) {
+                    pw.print(prefix);
+                    if (screenStates.length > 1) {
+                        printScreenLabel(pw, printedScreen != iscreen
+                                ? iscreen : STATE_NOTHING);
+                        printedScreen = iscreen;
+                    }
+                    if (memStates.length > 1) {
+                        printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0');
+                        printedMem = imem;
+                    }
+                    pw.print(": ");
+                    pw.print(count);
+                    pw.println(" samples:");
+                    dumpSysMemUsageCategory(pw, prefix, "  Cached", bucket,
+                            SYS_MEM_USAGE_CACHED_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Free", bucket,
+                            SYS_MEM_USAGE_FREE_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  ZRam", bucket,
+                            SYS_MEM_USAGE_ZRAM_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Kernel", bucket,
+                            SYS_MEM_USAGE_KERNEL_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Native", bucket,
+                            SYS_MEM_USAGE_NATIVE_MINIMUM);
+                }
+            }
+        }
+    }
+
     static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
             int[] memStates, int[] procStates) {
         final int NS = screenStates != null ? screenStates.length : 1;
@@ -1088,10 +1395,13 @@
         mTimePeriodStartClock = System.currentTimeMillis();
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+        mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
         mLongs.clear();
         mLongs.add(new long[LONGS_SIZE]);
         mNextLong = 0;
         Arrays.fill(mMemFactorDurations, 0);
+        mSysMemUsageTable = null;
+        mSysMemUsageTableSize = 0;
         mStartTime = 0;
         mReadError = null;
         mFlags = 0;
@@ -1220,12 +1530,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        long now = SystemClock.uptimeMillis();
+        writeToParcel(out, SystemClock.uptimeMillis(), flags);
+    }
+
+    /** @hide */
+    public void writeToParcel(Parcel out, long now, int flags) {
         out.writeInt(MAGIC);
         out.writeInt(PARCEL_VERSION);
         out.writeInt(STATE_COUNT);
         out.writeInt(ADJ_COUNT);
         out.writeInt(PSS_COUNT);
+        out.writeInt(SYS_MEM_USAGE_COUNT);
         out.writeInt(LONGS_SIZE);
 
         mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
@@ -1268,8 +1583,9 @@
         out.writeLong(mTimePeriodStartClock);
         out.writeLong(mTimePeriodStartRealtime);
         out.writeLong(mTimePeriodEndRealtime);
+        out.writeLong(mTimePeriodStartUptime);
+        out.writeLong(mTimePeriodEndUptime);
         out.writeString(mRuntime);
-        out.writeString(mWebView);
         out.writeInt(mFlags);
 
         out.writeInt(mLongs.size());
@@ -1287,6 +1603,13 @@
         }
         writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length);
 
+        out.writeInt(mSysMemUsageTableSize);
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": "
+                    + printLongOffset(mSysMemUsageTable[i]));
+            out.writeInt(mSysMemUsageTable[i]);
+        }
+
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
             writeCommonString(out, procMap.keyAt(ip));
@@ -1417,6 +1740,9 @@
         if (!readCheckedInt(in, PSS_COUNT, "pss count")) {
             return;
         }
+        if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) {
+            return;
+        }
         if (!readCheckedInt(in, LONGS_SIZE, "longs size")) {
             return;
         }
@@ -1427,8 +1753,9 @@
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = in.readLong();
         mTimePeriodEndRealtime = in.readLong();
+        mTimePeriodStartUptime = in.readLong();
+        mTimePeriodEndUptime = in.readLong();
         mRuntime = in.readString();
-        mWebView = in.readString();
         mFlags = in.readInt();
 
         final int NLONGS = in.readInt();
@@ -1447,6 +1774,12 @@
 
         readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
 
+        mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage");
+        if (mSysMemUsageTable == BAD_TABLE) {
+            return;
+        }
+        mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0;
+
         int NPROC = in.readInt();
         if (NPROC < 0) {
             mReadError = "bad process count: " + NPROC;
@@ -1826,6 +2159,10 @@
             boolean dumpAll, boolean activeOnly) {
         long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.println("System memory usage:");
+            dumpSysMemUsage(pw, "  ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
+        }
         ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
         boolean printedHeader = false;
         boolean sepNeeded = false;
@@ -2089,10 +2426,57 @@
         dumpTotalsLocked(pw, now);
     }
 
+    long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight,
+            long totalTime, long curTotalMem, int samples) {
+        if (memWeight != 0) {
+            long mem = (long)(memWeight * 1024 / totalTime);
+            pw.print(prefix);
+            pw.print(label);
+            pw.print(": ");
+            printSizeValue(pw, mem);
+            pw.print(" (");
+            pw.print(samples);
+            pw.print(" samples)");
+            pw.println();
+            return curTotalMem + mem;
+        }
+        return curTotalMem;
+    }
+
     void dumpTotalsLocked(PrintWriter pw, long now) {
         pw.println("Run time Stats:");
         dumpSingleTime(pw, "  ", mMemFactorDurations, mMemFactor, mStartTime, now);
         pw.println();
+        pw.println("Memory usage:");
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        long totalPss = 0;
+        totalPss = printMemoryCategory(pw, "  ", "Kernel ", totalMem.sysMemKernelWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Native ", totalMem.sysMemNativeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            // Skip restarting service state -- that is not actually a running process.
+            if (i != STATE_SERVICE_RESTARTING) {
+                totalPss = printMemoryCategory(pw, "  ", STATE_NAMES[i],
+                        totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
+                        totalMem.processStateSamples[i]);
+            }
+        }
+        totalPss = printMemoryCategory(pw, "  ", "Cached ", totalMem.sysMemCachedWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Free   ", totalMem.sysMemFreeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Z-Ram   ", totalMem.sysMemZRamWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        pw.print("  TOTAL  : ");
+        printSizeValue(pw, totalPss);
+        pw.println();
+        printMemoryCategory(pw, "  ", STATE_NAMES[STATE_SERVICE_RESTARTING],
+                totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
+                totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
+        pw.println();
         pw.print("          Start time: ");
         pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
         pw.println();
@@ -2118,8 +2502,6 @@
         }
         pw.print(' ');
         pw.print(mRuntime);
-        pw.print(' ');
-        pw.print(mWebView);
         pw.println();
     }
 
@@ -2208,7 +2590,7 @@
     public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
         final long now = SystemClock.uptimeMillis();
         final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
-        pw.println("vers,4");
+        pw.println("vers,5");
         pw.print("period,"); pw.print(mTimePeriodStartClockStr);
         pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
         pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2229,7 +2611,7 @@
             pw.print(",partial");
         }
         pw.println();
-        pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
+        pw.print("config,"); pw.println(mRuntime);
         for (int ip=0; ip<pkgMap.size(); ip++) {
             final String pkgName = pkgMap.keyAt(ip);
             if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -2362,6 +2744,53 @@
         pw.print("total");
         dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.print("sysmemusage");
+            for (int i=0; i<mSysMemUsageTableSize; i++) {
+                int off = mSysMemUsageTable[i];
+                int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                pw.print(",");
+                printProcStateTag(pw, type);
+                for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) {
+                    if (j > SYS_MEM_USAGE_CACHED_MINIMUM) {
+                        pw.print(":");
+                    }
+                    pw.print(getLong(off, j));
+                }
+            }
+        }
+        pw.println();
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        pw.print("weights,");
+        pw.print(totalMem.totalTime);
+        pw.print(",");
+        pw.print(totalMem.sysMemCachedWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemFreeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemZRamWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemKernelWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemNativeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            pw.print(",");
+            pw.print(totalMem.processStateWeight[i]);
+            pw.print(":");
+            pw.print(totalMem.processStateSamples[i]);
+        }
         pw.println();
     }
 
@@ -2452,6 +2881,15 @@
         }
     }
 
+    final public static class ProcessStateHolder {
+        public final int appVersion;
+        public ProcessStats.ProcessState state;
+
+        public ProcessStateHolder(int _appVersion) {
+            appVersion = _appVersion;
+        }
+    }
+
     public static final class ProcessState extends DurationsTable {
         public ProcessState mCommonProcess;
         public final String mPackage;
@@ -2660,7 +3098,7 @@
          * @param pkgList Processes to update.
          */
         public void setState(int state, int memFactor, long now,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             if (state < 0) {
                 state = mNumStartedServices > 0
                         ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
@@ -2770,7 +3208,7 @@
         }
 
         public void addPss(long pss, long uss, boolean always,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             if (!always) {
                 if (mLastPssState == mCurState && SystemClock.uptimeMillis()
@@ -2845,7 +3283,7 @@
             }
         }
 
-        public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveWake++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2857,7 +3295,7 @@
             }
         }
 
-        public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveCpu++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2888,7 +3326,7 @@
             }
         }
 
-        public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) {
+        public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
             ensureNotDead();
             mCommonProcess.addCachedKill(1, pss, pss, pss);
             if (!mCommonProcess.mMultiPackage) {
@@ -2925,8 +3363,10 @@
             return this;
         }
 
-        private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) {
-            ProcessState proc = pkgList.valueAt(index);
+        private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
+                int index) {
+            ProcessStateHolder holder = pkgList.valueAt(index);
+            ProcessState proc = holder.state;
             if (mDead && proc.mCommonProcess != proc) {
                 // Somehow we are contining to use a process state that is dead, because
                 // it was not being told it was active during the last commit.  We can recover
@@ -2959,7 +3399,7 @@
                     throw new IllegalStateException("Didn't create per-package process "
                             + proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
                 }
-                pkgList.setValueAt(index, proc);
+                holder.state = proc;
             }
             return proc;
         }
@@ -3351,4 +3791,27 @@
             }
         }
     }
+
+    public static class TotalMemoryUseCollection {
+        final int[] screenStates;
+        final int[] memStates;
+
+        public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) {
+            screenStates = _screenStates;
+            memStates = _memStates;
+        }
+
+        public long totalTime;
+        public long[] processStatePss = new long[STATE_COUNT];
+        public double[] processStateWeight = new double[STATE_COUNT];
+        public long[] processStateTime = new long[STATE_COUNT];
+        public int[] processStateSamples = new int[STATE_COUNT];
+        public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        public double sysMemCachedWeight;
+        public double sysMemFreeWeight;
+        public double sysMemZRamWeight;
+        public double sysMemKernelWeight;
+        public double sysMemNativeWeight;
+        public int sysMemSamples;
+    }
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 591267e..9d42738 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -259,11 +259,11 @@
         super.onRestoreInstanceState(savedInstanceState);
         if (mAlwaysUseOption) {
             final int checkedPos = mListView.getCheckedItemPosition();
-            final boolean enabled = checkedPos != ListView.INVALID_POSITION;
+            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
             mLastSelected = checkedPos;
-            mAlwaysButton.setEnabled(enabled);
-            mOnceButton.setEnabled(enabled);
-            if (enabled) {
+            setAlwaysButtonEnabled(hasValidSelection, checkedPos);
+            mOnceButton.setEnabled(hasValidSelection);
+            if (hasValidSelection) {
                 mListView.setSelection(checkedPos);
             }
         }
@@ -274,7 +274,7 @@
         final int checkedPos = mListView.getCheckedItemPosition();
         final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
         if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
-            mAlwaysButton.setEnabled(hasValidSelection);
+            setAlwaysButtonEnabled(hasValidSelection, checkedPos);
             mOnceButton.setEnabled(hasValidSelection);
             if (hasValidSelection) {
                 mListView.smoothScrollToPosition(checkedPos);
@@ -285,6 +285,17 @@
         }
     }
 
+    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos) {
+        boolean enabled = false;
+        if (hasValidSelection) {
+            ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos);
+            if (ri.targetUserId == UserHandle.USER_CURRENT) {
+                enabled = true;
+            }
+        }
+        mAlwaysButton.setEnabled(enabled);
+    }
+
     public void onButtonClick(View v) {
         final int id = v.getId();
         startSelected(mListView.getCheckedItemPosition(), id == R.id.button_always);
@@ -484,8 +495,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/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 87a80ac..7bd316f 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -830,7 +830,9 @@
     }
 
     public boolean isShowing() {
-        return mNowShowing && getHideOffset() < getHeight();
+        final int height = getHeight();
+        // Take into account the case where the bar has a 0 height due to not being measured yet.
+        return mNowShowing && (height == 0 || getHideOffset() < height);
     }
 
     void animateToMode(boolean toActionMode) {
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..643225d 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.backup;
 
+import android.app.backup.RestoreDescription;
 import android.app.backup.RestoreSet;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -168,17 +169,30 @@
     int startRestore(long token, in PackageInfo[] packages);
 
     /**
-     * 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).
+     * Get the package name of the next application with data in the backup store, plus
+     * a description of the structure of the restored archive: either TYPE_KEY_VALUE for
+     * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
+     *
+     * <p>If the package name in the returned RestoreDescription object is the singleton
+     * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
+     * in the current restore session: all packages described in startRestore() have been
+     * processed.
+     *
+     * <p>If this method returns {@code null}, it means that a transport-level error has
+     * occurred and the entire restore operation should be abandoned.
+     *
+     * @return A RestoreDescription object containing the name of one of the packages
+     *   supplied to {@link #startRestore} plus an indicator of the data type of that
+     *   restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
+     *   no more packages can be restored in this session; or {@code null} to indicate
+     *   a transport-level error.
      */
-    String nextRestorePackage();
+    RestoreDescription nextRestorePackage();
 
     /**
      * 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);
 
@@ -187,4 +201,59 @@
      * freeing any resources and connections used during the restore process.
      */
     void finishRestore();
+
+    // full backup stuff
+
+    long requestFullBackupTime();
+    int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket);
+    int sendBackupData(int numBytes);
+
+    // full restore stuff
+
+    /**
+     * Ask the transport to provide data for the "current" package being restored.  This
+     * is the package that was just reported by {@link #nextRestorePackage()} as having
+     * {@link RestoreDescription#TYPE_FULL_STREAM} data.
+     *
+     * The transport writes some data to the socket supplied to this call, and returns
+     * the number of bytes written.  The system will then read that many bytes and
+     * stream them to the application's agent for restore, then will call this method again
+     * to receive the next chunk of the archive.  This sequence will be repeated until the
+     * transport returns zero indicating that all of the package's data has been delivered
+     * (or returns a negative value indicating some sort of hard error condition at the
+     * transport level).
+     *
+     * <p>After this method returns zero, the system will then call
+     * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+     * application, and the sequence begins again.
+     *
+     * <p>The transport should always close this socket when returning from this method.
+     * Do not cache this socket across multiple calls or you may leak file descriptors.
+     *
+     * @param socket The file descriptor that the transport will use for delivering the
+     *    streamed archive.  The transport must close this socket in all cases when returning
+     *    from this method.
+     * @return 0 when no more data for the current package is available.  A positive value
+     *    indicates the presence of that many bytes to be delivered to the app.  Any negative
+     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
+     *    indicating a fatal error condition that precludes further restore operations
+     *    on the current dataset.
+     */
+    int getNextFullRestoreDataChunk(in ParcelFileDescriptor socket);
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    int abortFullRestore();
+
 }
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 446ef55..b098de8 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -18,6 +18,8 @@
 
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
 import android.app.backup.RestoreSet;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,12 +35,17 @@
 
 import com.android.org.bouncycastle.util.encoders.Base64;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
 
 import static android.system.OsConstants.*;
 
@@ -47,7 +54,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;
 
@@ -57,55 +64,84 @@
     private static final String TRANSPORT_DESTINATION_STRING
             = "Backing up to debug-only private cache";
 
+    private static final String INCREMENTAL_DIR = "_delta";
+    private static final String FULL_DATA_DIR = "_full";
+
     // The currently-active restore set always has the same (nonzero!) token
     private static final long CURRENT_SET_TOKEN = 1;
 
     private Context mContext;
     private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
     private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
+    private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR);
+    private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR);
 
     private PackageInfo[] mRestorePackages = null;
     private int mRestorePackage = -1;  // Index into mRestorePackages
-    private File mRestoreDataDir;
+    private int mRestoreType;
+    private File mRestoreSetDir;
+    private File mRestoreSetIncrementalDir;
+    private File mRestoreSetFullDir;
     private long mRestoreToken;
 
+    // Additional bookkeeping for full backup
+    private String mFullTargetPackage;
+    private ParcelFileDescriptor mSocket;
+    private FileInputStream mSocketInputStream;
+    private BufferedOutputStream mFullBackupOutputStream;
+    private byte[] mFullBackupBuffer;
+
+    private File mFullRestoreSetDir;
+    private HashSet<String> mFullRestorePackages;
+    private FileInputStream mCurFullRestoreStream;
+    private FileOutputStream mFullRestoreSocketStream;
+    private byte[] mFullRestoreBuffer;
 
     public LocalTransport(Context context) {
         mContext = context;
         mCurrentSetDir.mkdirs();
+        mCurrentSetFullDir.mkdir();
+        mCurrentSetIncrementalDir.mkdir();
         if (!SELinux.restorecon(mCurrentSetDir)) {
             Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
         }
     }
 
+    @Override
     public String name() {
         return new ComponentName(mContext, this.getClass()).flattenToShortString();
     }
 
+    @Override
     public Intent configurationIntent() {
         // The local transport is not user-configurable
         return null;
     }
 
+    @Override
     public String currentDestinationString() {
         return TRANSPORT_DESTINATION_STRING;
     }
 
+    @Override
     public String transportDirName() {
         return TRANSPORT_DIR_NAME;
     }
 
+    @Override
     public long requestBackupTime() {
         // any time is a good time for local backup
         return 0;
     }
 
+    @Override
     public int initializeDevice() {
         if (DEBUG) Log.v(TAG, "wiping all data");
         deleteContents(mCurrentSetDir);
-        return BackupConstants.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
         if (DEBUG) {
             try {
@@ -118,7 +154,7 @@
             }
         }
 
-        File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
+        File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
         packageDir.mkdirs();
 
         // Each 'record' in the restore set is kept in its own file, named by
@@ -165,7 +201,7 @@
                         entity.write(buf, 0, dataSize);
                     } catch (IOException e) {
                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
-                        return BackupConstants.TRANSPORT_ERROR;
+                        return TRANSPORT_ERROR;
                     } finally {
                         entity.close();
                     }
@@ -173,11 +209,11 @@
                     entityFile.delete();
                 }
             }
-            return BackupConstants.TRANSPORT_OK;
+            return 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 TRANSPORT_ERROR;
         }
     }
 
@@ -196,10 +232,11 @@
         }
     }
 
+    @Override
     public int clearBackupData(PackageInfo packageInfo) {
         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
 
-        File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
+        File packageDir = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
         final File[] fileset = packageDir.listFiles();
         if (fileset != null) {
             for (File f : fileset) {
@@ -207,27 +244,129 @@
             }
             packageDir.delete();
         }
-        return BackupConstants.TRANSPORT_OK;
+
+        packageDir = new File(mCurrentSetFullDir, packageInfo.packageName);
+        final File[] tarballs = packageDir.listFiles();
+        if (tarballs != null) {
+            for (File f : tarballs) {
+                f.delete();
+            }
+            packageDir.delete();
+        }
+
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int finishBackup() {
         if (DEBUG) Log.v(TAG, "finishBackup()");
-        return BackupConstants.TRANSPORT_OK;
+        if (mSocket != null) {
+            if (DEBUG) {
+                Log.v(TAG, "Concluding full backup of " + mFullTargetPackage);
+            }
+            try {
+                mFullBackupOutputStream.flush();
+                mFullBackupOutputStream.close();
+                mSocketInputStream = null;
+                mFullTargetPackage = null;
+                mSocket.close();
+            } catch (IOException e) {
+                return TRANSPORT_ERROR;
+            } finally {
+                mSocket = null;
+            }
+        }
+        return TRANSPORT_OK;
     }
 
+    // ------------------------------------------------------------------------------------
+    // Full backup handling
+
+    @Override
+    public long requestFullBackupTime() {
+        return 0;
+    }
+
+    @Override
+    public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
+        if (mSocket != null) {
+            Log.e(TAG, "Attempt to initiate full backup while one is in progress");
+            return TRANSPORT_ERROR;
+        }
+
+        if (DEBUG) {
+            Log.i(TAG, "performFullBackup : " + targetPackage);
+        }
+
+        // We know a priori that we run in the system process, so we need to make
+        // sure to dup() our own copy of the socket fd.  Transports which run in
+        // their own processes must not do this.
+        try {
+            mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+            mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to process socket for full backup");
+            return TRANSPORT_ERROR;
+        }
+
+        mFullTargetPackage = targetPackage.packageName;
+        FileOutputStream tarstream;
+        try {
+            File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
+            tarstream = new FileOutputStream(tarball);
+        } catch (FileNotFoundException e) {
+            return TRANSPORT_ERROR;
+        }
+        mFullBackupOutputStream = new BufferedOutputStream(tarstream);
+        mFullBackupBuffer = new byte[4096];
+
+        return TRANSPORT_OK;
+    }
+
+    @Override
+    public int sendBackupData(int numBytes) {
+        if (mFullBackupBuffer == null) {
+            Log.w(TAG, "Attempted sendBackupData before performFullBackup");
+            return TRANSPORT_ERROR;
+        }
+
+        if (numBytes > mFullBackupBuffer.length) {
+            mFullBackupBuffer = new byte[numBytes];
+        }
+        while (numBytes > 0) {
+            try {
+            int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, numBytes);
+            if (nRead < 0) {
+                // Something went wrong if we expect data but saw EOD
+                Log.w(TAG, "Unexpected EOD; failing backup");
+                return TRANSPORT_ERROR;
+            }
+            mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
+            numBytes -= nRead;
+            } catch (IOException e) {
+                Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
+                return TRANSPORT_ERROR;
+            }
+        }
+        return TRANSPORT_OK;
+    }
+
+    // ------------------------------------------------------------------------------------
     // Restore handling
     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; 
-    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+
+    @Override
+    public RestoreSet[] getAvailableRestoreSets() {
         long[] existing = new long[POSSIBLE_SETS.length + 1];
         int num = 0;
 
-        // see which possible non-current sets exist, then put the current set at the end
+        // see which possible non-current sets exist...
         for (long token : POSSIBLE_SETS) {
             if ((new File(mDataDir, Long.toString(token))).exists()) {
                 existing[num++] = token;
             }
         }
-        // and always the currently-active set last
+        // ...and always the currently-active set last
         existing[num++] = CURRENT_SET_TOKEN;
 
         RestoreSet[] available = new RestoreSet[num];
@@ -237,40 +376,69 @@
         return available;
     }
 
+    @Override
     public long getCurrentRestoreSet() {
         // The current restore set always has the same token
         return CURRENT_SET_TOKEN;
     }
 
+    @Override
     public int startRestore(long token, PackageInfo[] packages) {
         if (DEBUG) Log.v(TAG, "start restore " + token);
         mRestorePackages = packages;
         mRestorePackage = -1;
         mRestoreToken = token;
-        mRestoreDataDir = new File(mDataDir, Long.toString(token));
-        return BackupConstants.TRANSPORT_OK;
+        mRestoreSetDir = new File(mDataDir, Long.toString(token));
+        mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
+        mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
+        return TRANSPORT_OK;
     }
 
-    public String nextRestorePackage() {
+    @Override
+    public RestoreDescription nextRestorePackage() {
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
+
+        boolean found = false;
         while (++mRestorePackage < mRestorePackages.length) {
             String name = mRestorePackages[mRestorePackage].packageName;
+
+            // If we have key/value data for this package, deliver that
             // skip packages where we have a data dir but no actual contents
-            String[] contents = (new File(mRestoreDataDir, name)).list();
+            String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
             if (contents != null && contents.length > 0) {
-                if (DEBUG) Log.v(TAG, "  nextRestorePackage() = " + name);
-                return name;
+                if (DEBUG) Log.v(TAG, "  nextRestorePackage(TYPE_KEY_VALUE) = " + name);
+                mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
+                found = true;
+            }
+
+            if (!found) {
+                // No key/value data; check for [non-empty] full data
+                File maybeFullData = new File(mRestoreSetFullDir, name);
+                if (maybeFullData.length() > 0) {
+                    if (DEBUG) Log.v(TAG, "  nextRestorePackage(TYPE_FULL_STREAM) = " + name);
+                    mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
+                    mCurFullRestoreStream = null;   // ensure starting from the ground state
+                    found = true;
+                }
+            }
+
+            if (found) {
+                return new RestoreDescription(name, mRestoreType);
             }
         }
 
         if (DEBUG) Log.v(TAG, "  no more packages to restore");
-        return "";
+        return RestoreDescription.NO_MORE_PACKAGES;
     }
 
+    @Override
     public int getRestoreData(ParcelFileDescriptor outFd) {
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
-        File packageDir = new File(mRestoreDataDir, mRestorePackages[mRestorePackage].packageName);
+        if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
+            throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
+        }
+        File packageDir = new File(mRestoreSetDir, mRestorePackages[mRestorePackage].packageName);
 
         // The restore set is the concatenation of the individual record blobs,
         // each of which is a file in the package's directory.  We return the
@@ -280,7 +448,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 TRANSPORT_ERROR;
         }
 
         // We expect at least some data if the directory exists in the first place
@@ -301,10 +469,10 @@
                     in.close();
                 }
             }
-            return BackupConstants.TRANSPORT_OK;
+            return TRANSPORT_OK;
         } catch (IOException e) {
             Log.e(TAG, "Unable to read backup records", e);
-            return BackupConstants.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
     }
 
@@ -341,7 +509,124 @@
         return contents;
     }
 
+    @Override
     public void finishRestore() {
         if (DEBUG) Log.v(TAG, "finishRestore()");
+        if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
+            resetFullRestoreState();
+        }
+        mRestoreType = 0;
     }
+
+    // ------------------------------------------------------------------------------------
+    // Full restore handling
+
+    private void resetFullRestoreState() {
+        try {
+        mCurFullRestoreStream.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to close full restore input stream");
+        }
+        mCurFullRestoreStream = null;
+        mFullRestoreSocketStream = null;
+        mFullRestoreBuffer = null;
+    }
+
+    /**
+     * Ask the transport to provide data for the "current" package being restored.  The
+     * transport then writes some data to the socket supplied to this call, and returns
+     * the number of bytes written.  The system will then read that many bytes and
+     * stream them to the application's agent for restore, then will call this method again
+     * to receive the next chunk of the archive.  This sequence will be repeated until the
+     * transport returns zero indicating that all of the package's data has been delivered
+     * (or returns a negative value indicating some sort of hard error condition at the
+     * transport level).
+     *
+     * <p>After this method returns zero, the system will then call
+     * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+     * application, and the sequence begins again.
+     *
+     * @param socket The file descriptor that the transport will use for delivering the
+     *    streamed archive.
+     * @return 0 when no more data for the current package is available.  A positive value
+     *    indicates the presence of that much data to be delivered to the app.  A negative
+     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
+     *    indicating a fatal error condition that precludes further restore operations
+     *    on the current dataset.
+     */
+    @Override
+    public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("Asked for full restore data for non-stream package");
+        }
+
+        // first chunk?
+        if (mCurFullRestoreStream == null) {
+            final String name = mRestorePackages[mRestorePackage].packageName;
+            if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
+            File dataset = new File(mRestoreSetFullDir, name);
+            try {
+                mCurFullRestoreStream = new FileInputStream(dataset);
+            } catch (IOException e) {
+                // If we can't open the target package's tarball, we return the single-package
+                // error code and let the caller go on to the next package.
+                Log.e(TAG, "Unable to read archive for " + name);
+                return TRANSPORT_PACKAGE_REJECTED;
+            }
+            mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
+            mFullRestoreBuffer = new byte[32*1024];
+        }
+
+        int nRead;
+        try {
+            nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
+            if (nRead < 0) {
+                // EOF: tell the caller we're done
+                nRead = NO_MORE_DATA;
+            } else if (nRead == 0) {
+                // This shouldn't happen when reading a FileInputStream; we should always
+                // get either a positive nonzero byte count or -1.  Log the situation and
+                // treat it as EOF.
+                Log.w(TAG, "read() of archive file returned 0; treating as EOF");
+                nRead = NO_MORE_DATA;
+            } else {
+                if (DEBUG) {
+                    Log.i(TAG, "   delivering restore chunk: " + nRead);
+                }
+                mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
+            }
+        } catch (IOException e) {
+            return TRANSPORT_ERROR;  // Hard error accessing the file; shouldn't happen
+        } finally {
+            // Most transports will need to explicitly close 'socket' here, but this transport
+            // is in the same process as the caller so it can leave it up to the backup manager
+            // to manage both socket fds.
+        }
+
+        return nRead;
+    }
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    @Override
+    public int abortFullRestore() {
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("abortFullRestore() but not currently restoring");
+        }
+        resetFullRestoreState();
+        mRestoreType = 0;
+        return TRANSPORT_OK;
+    }
+
 }
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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 240d520..02e4b3f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
 import android.os.BadParcelableException;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -88,7 +89,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 106 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 107 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -284,6 +285,13 @@
     int mWifiState = -1;
     final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES];
 
+    int mWifiSupplState = -1;
+    final StopwatchTimer[] mWifiSupplStateTimer = new StopwatchTimer[NUM_WIFI_SUPPL_STATES];
+
+    int mWifiSignalStrengthBin = -1;
+    final StopwatchTimer[] mWifiSignalStrengthsTimer =
+            new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS];
+
     boolean mBluetoothOn;
     StopwatchTimer mBluetoothOnTimer;
 
@@ -291,6 +299,7 @@
     final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
 
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+    long mMobileRadioActiveStartTime;
     StopwatchTimer mMobileRadioActiveTimer;
     StopwatchTimer mMobileRadioActivePerAppTimer;
     LongSamplingCounter mMobileRadioActiveAdjustedTime;
@@ -1425,10 +1434,6 @@
             return 0;
         }
 
-        long getLastUpdateTimeMs() {
-            return mUpdateTime;
-        }
-
         void stopRunningLocked(long elapsedRealtimeMs) {
             // Ignore attempt to stop a timer that isn't running
             if (mNesting == 0) {
@@ -2089,7 +2094,9 @@
             if (mHistoryLastWritten.batteryLevel == cur.batteryLevel &&
                     (dataSize >= MAX_MAX_HISTORY_BUFFER
                             || ((mHistoryLastWritten.states^cur.states)
-                                    & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
+                                    & HistoryItem.MOST_INTERESTING_STATES) == 0
+                            || ((mHistoryLastWritten.states2^cur.states2)
+                                    & HistoryItem.MOST_INTERESTING_STATES2) == 0)) {
                 return;
             }
 
@@ -2790,11 +2797,11 @@
                     powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
                             || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
             if (active) {
-                realElapsedRealtimeMs = elapsedRealtime;
+                mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime;
                 mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
             } else {
                 realElapsedRealtimeMs = timestampNs / (1000*1000);
-                long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+                long lastUpdateTimeMs = mMobileRadioActiveStartTime;
                 if (realElapsedRealtimeMs < lastUpdateTimeMs) {
                     Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
                             + " is before start time " + lastUpdateTimeMs);
@@ -2866,7 +2873,7 @@
         }
     }
 
-    void stopAllSignalStrengthTimersLocked(int except) {
+    void stopAllPhoneSignalStrengthTimersLocked(int except) {
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             if (i == except) {
@@ -2962,13 +2969,13 @@
                 if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
                 }
-                mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
-                        | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
+                mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
                 if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
                         + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
             } else {
-                stopAllSignalStrengthTimersLocked(-1);
+                stopAllPhoneSignalStrengthTimersLocked(-1);
             }
             mPhoneSignalStrengthBin = strengthBin;
         }
@@ -3068,7 +3075,7 @@
         if (!mWifiOn) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
-            mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG;
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
@@ -3081,7 +3088,7 @@
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
         if (mWifiOn) {
-            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
@@ -3174,7 +3181,7 @@
         if (!mGlobalWifiRunning) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
-            mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
@@ -3212,7 +3219,7 @@
         if (mGlobalWifiRunning) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
-            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
                     + Integer.toHexString(mHistoryCur.states));
             addHistoryRecordLocked(elapsedRealtime, uptime);
@@ -3240,6 +3247,64 @@
         }
     }
 
+    public void noteWifiSupplicantStateChangedLocked(int supplState, boolean failedAuth) {
+        if (DEBUG) Log.i(TAG, "WiFi suppl state -> " + supplState);
+        if (mWifiSupplState != supplState) {
+            final long elapsedRealtime = SystemClock.elapsedRealtime();
+            final long uptime = SystemClock.uptimeMillis();
+            if (mWifiSupplState >= 0) {
+                mWifiSupplStateTimer[mWifiSupplState].stopRunningLocked(elapsedRealtime);
+            }
+            mWifiSupplState = supplState;
+            mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtime);
+            mHistoryCur.states2 =
+                    (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+                    | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+    }
+
+    void stopAllWifiSignalStrengthTimersLocked(int except) {
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            if (i == except) {
+                continue;
+            }
+            while (mWifiSignalStrengthsTimer[i].isRunningLocked()) {
+                mWifiSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteWifiRssiChangedLocked(int newRssi) {
+        int strengthBin = WifiManager.calculateSignalLevel(newRssi, NUM_WIFI_SIGNAL_STRENGTH_BINS);
+        if (DEBUG) Log.i(TAG, "WiFi rssi -> " + newRssi + " bin=" + strengthBin);
+        if (mWifiSignalStrengthBin != strengthBin) {
+            final long elapsedRealtime = SystemClock.elapsedRealtime();
+            final long uptime = SystemClock.uptimeMillis();
+            if (mWifiSignalStrengthBin >= 0) {
+                mWifiSignalStrengthsTimer[mWifiSignalStrengthBin].stopRunningLocked(
+                        elapsedRealtime);
+            }
+            if (strengthBin >= 0) {
+                if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
+                    mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                }
+                mHistoryCur.states2 =
+                        (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+                if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            } else {
+                stopAllWifiSignalStrengthTimersLocked(-1);
+            }
+            mWifiSignalStrengthBin = strengthBin;
+        }
+    }
+
     public void noteBluetoothOnLocked() {
         if (!mBluetoothOn) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -3583,6 +3648,26 @@
         return mWifiStateTimer[wifiState].getCountLocked(which);
     }
 
+    @Override public long getWifiSupplStateTime(int state,
+            long elapsedRealtimeUs, int which) {
+        return mWifiSupplStateTimer[state].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiSupplStateCount(int state, int which) {
+        return mWifiSupplStateTimer[state].getCountLocked(which);
+    }
+
+    @Override public long getWifiSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which) {
+        return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiSignalStrengthCount(int strengthBin, int which) {
+        return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which);
+    }
+
     @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) {
         return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -5583,6 +5668,13 @@
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i] = new StopwatchTimer(null, -700-i, null, mOnBatteryTimeBase);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i, null,
+                    mOnBatteryTimeBase);
+        }
         mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase);
@@ -5859,6 +5951,12 @@
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             mWifiStateTimer[i].reset(false);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].reset(false);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].reset(false);
+        }
         mBluetoothOnTimer.reset(false);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].reset(false);
@@ -7015,6 +7113,12 @@
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             mWifiStateTimer[i].readSummaryFromParcelLocked(in);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].readSummaryFromParcelLocked(in);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
+        }
         mBluetoothOn = false;
         mBluetoothOnTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
@@ -7267,6 +7371,12 @@
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
         mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -7539,6 +7649,14 @@
             mWifiStateTimer[i] = new StopwatchTimer(null, -600-i,
                     null, mOnBatteryTimeBase, in);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i] = new StopwatchTimer(null, -700-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i,
+                    null, mOnBatteryTimeBase, in);
+        }
         mBluetoothOn = false;
         mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase, in);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
@@ -7670,6 +7788,12 @@
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             mWifiStateTimer[i].writeToParcel(out, uSecRealtime);
         }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
+        }
         mBluetoothOnTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
             mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime);
@@ -7771,7 +7895,7 @@
             pr.println("*** Phone timer:");
             mPhoneOnTimer.logState(pr, "  ");
             for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
-                pr.println("*** Signal strength #" + i + ":");
+                pr.println("*** Phone signal strength #" + i + ":");
                 mPhoneSignalStrengthsTimer[i].logState(pr, "  ");
             }
             pr.println("*** Signal scanning :");
@@ -7793,6 +7917,14 @@
                 pr.println("*** Wifi state #" + i + ":");
                 mWifiStateTimer[i].logState(pr, "  ");
             }
+            for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+                pr.println("*** Wifi suppl state #" + i + ":");
+                mWifiSupplStateTimer[i].logState(pr, "  ");
+            }
+            for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+                pr.println("*** Wifi signal strength #" + i + ":");
+                mWifiSignalStrengthsTimer[i].logState(pr, "  ");
+            }
             pr.println("*** Bluetooth timer:");
             mBluetoothOnTimer.logState(pr, "  ");
             for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 58cd60d..e58d68f 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -442,8 +442,9 @@
                 final String[] procStatsString = mProcessFullStatsStringData;
                 final long[] procStats = mProcessFullStatsData;
                 st.base_uptime = SystemClock.uptimeMillis();
-                if (Process.readProcFile(st.statFile.toString(),
-                        PROCESS_FULL_STATS_FORMAT, procStatsString,
+                String path = st.statFile.toString();
+                //Slog.d(TAG, "Reading proc file: " + path);
+                if (Process.readProcFile(path, PROCESS_FULL_STATS_FORMAT, procStatsString,
                         procStats, null)) {
                     // This is a possible way to filter out processes that
                     // are actually kernel threads...  do we want to?  Some
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/policy/IFaceLockInterface.aidl b/core/java/com/android/internal/policy/IFaceLockInterface.aidl
index 017801b..bc1f002 100644
--- a/core/java/com/android/internal/policy/IFaceLockInterface.aidl
+++ b/core/java/com/android/internal/policy/IFaceLockInterface.aidl
@@ -23,6 +23,7 @@
     void startUi(IBinder containingWindowToken, int x, int y, int width, int height,
             boolean useLiveliness);
     void stopUi();
+    void startWithoutUi();
     void registerCallback(IFaceLockCallback cb);
     void unregisterCallback(IFaceLockCallback cb);
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a01e9b7..84bd443 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -24,9 +24,6 @@
 {
     void setIcon(int index, in StatusBarIcon icon);
     void removeIcon(int index);
-    void addNotification(in StatusBarNotification notification);
-    void updateNotification(in StatusBarNotification notification);
-    void removeNotification(String key);
     void disable(int state);
     void animateExpandNotificationsPanel();
     void animateExpandSettingsPanel();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a3b417f..f3430e7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -39,8 +39,7 @@
     // ---- Methods below are for use by the status bar policy services ----
     // You need the STATUS_BAR_SERVICE permission
     void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
-            out List<StatusBarNotification> notifications, out int[] switches,
-            out List<IBinder> binders);
+            out int[] switches, out List<IBinder> binders);
     void onPanelRevealed();
     void onPanelHidden();
     void onNotificationClick(String key);
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/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d31c5cc..5bd6f52 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1460,4 +1460,11 @@
         }
         return activeTrustAgents;
     }
+
+    /**
+     * @see android.app.trust.TrustManager#reportRequireCredentialEntry(int)
+     */
+    public void requireCredentialEntry(int userId) {
+        getTrustManager().reportRequireCredentialEntry(userId);
+    }
 }
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 002573e..97b1634 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -45,10 +45,9 @@
         /**
          * Called when the layout has been swiped and the position of the window should change.
          *
-         * @param progress A number in [-1, 1] representing how far to the left
-         * or right the window has been swiped. Negative values are swipes
-         * left, and positives are right.
-         * @param translate A number in [-w, w], where w is the width of the
+         * @param progress A number in [0, 1] representing how far to the
+         * right the window has been swiped
+         * @param translate A number in [0, w], where w is the width of the
          * layout. This is equivalent to progress * layout.getWidth().
          */
         void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate);
@@ -207,7 +206,7 @@
 
     private void setProgress(float deltaX) {
         mTranslationX = deltaX;
-        if (mProgressListener != null) {
+        if (mProgressListener != null && deltaX >= 0)  {
             mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX);
         }
     }
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index bed0b88..30cd11b 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -152,6 +152,11 @@
 
         a.recycle();
 
+        if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
+            mToolbar.setNavigationContentDescription(
+                    getContext().getResources().getText(R.string.action_bar_up_description));
+        }
+
         mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
             final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
                     0, android.R.id.home, 0, 0, mTitle);
@@ -480,7 +485,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/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java
index 87a50a9..fda6479 100644
--- a/core/java/com/android/server/SystemServiceManager.java
+++ b/core/java/com/android/server/SystemServiceManager.java
@@ -50,8 +50,19 @@
      * @return The service instance.
      */
     @SuppressWarnings("unchecked")
-    public SystemService startService(String className) throws ClassNotFoundException {
-        return startService((Class<SystemService>) Class.forName(className));
+    public SystemService startService(String className) {
+        final Class<SystemService> serviceClass;
+        try {
+            serviceClass = (Class<SystemService>)Class.forName(className);
+        } catch (ClassNotFoundException ex) {
+            Slog.i(TAG, "Starting " + className);
+            throw new RuntimeException("Failed to create service " + className
+                    + ": service class not found, usually indicates that the caller should "
+                    + "have called PackageManager.hasSystemFeature() to check whether the "
+                    + "feature is available on this device before trying to start the "
+                    + "services that implement it", ex);
+        }
+        return startService(serviceClass);
     }
 
     /**
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index 430dd63..3d9fb5c 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -18,6 +18,7 @@
 
 import android.net.INetworkManagementEventObserver;
 import android.net.LinkAddress;
+import android.net.RouteInfo;
 
 /**
  * Base {@link INetworkManagementEventObserver} that provides no-op
@@ -70,4 +71,14 @@
     public void interfaceDnsServerInfo(String iface, long lifetime, String[] servers) {
         // default no-op
     }
+
+    @Override
+    public void routeUpdated(RouteInfo route) {
+        // default no-op
+    }
+
+    @Override
+    public void routeRemoved(RouteInfo route) {
+        // default no-op
+    }
 }
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
new file mode 100644
index 0000000..ff905bb
--- /dev/null
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -0,0 +1,365 @@
+/*
+ * 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.net;
+
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Keeps track of link configuration received from Netlink.
+ *
+ * Instances of this class are expected to be owned by subsystems such as Wi-Fi
+ * or Ethernet that manage one or more network interfaces. Each interface to be
+ * tracked needs its own {@code NetlinkTracker}.
+ *
+ * An instance of this class is constructed by passing in an interface name and
+ * a callback. The owner is then responsible for registering the tracker with
+ * NetworkManagementService. When the class receives update notifications from
+ * the NetworkManagementService notification threads, it applies the update to
+ * its local LinkProperties, and if something has changed, notifies its owner of
+ * the update via the callback.
+ *
+ * The owner can then call {@code getLinkProperties()} in order to find out
+ * what changed. If in the meantime the LinkProperties stored here have changed,
+ * this class will return the current LinkProperties. Because each change
+ * triggers an update callback after the change is made, the owner may get more
+ * callbacks than strictly necessary (some of which may be no-ops), but will not
+ * be out of sync once all callbacks have been processed.
+ *
+ * Threading model:
+ *
+ * - The owner of this class is expected to create it, register it, and call
+ *   getLinkProperties or clearLinkProperties on its thread.
+ * - Most of the methods in the class are inherited from BaseNetworkObserver
+ *   and are called by NetworkManagementService notification threads.
+ * - All accesses to mLinkProperties must be synchronized(this). All the other
+ *   member variables are immutable once the object is constructed.
+ *
+ * This class currently tracks IPv4 and IPv6 addresses. In the future it will
+ * track routes and DNS servers.
+ *
+ * @hide
+ */
+public class NetlinkTracker extends BaseNetworkObserver {
+
+    private final String TAG;
+
+    public interface Callback {
+        public void update();
+    }
+
+    private final String mInterfaceName;
+    private final Callback mCallback;
+    private final LinkProperties mLinkProperties;
+    private DnsServerRepository mDnsServerRepository;
+
+    private static final boolean DBG = true;
+
+    public NetlinkTracker(String iface, Callback callback) {
+        TAG = "NetlinkTracker/" + iface;
+        mInterfaceName = iface;
+        mCallback = callback;
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+        mDnsServerRepository = new DnsServerRepository();
+    }
+
+    private void maybeLog(String operation, String iface, LinkAddress address) {
+        if (DBG) {
+            Log.d(TAG, operation + ": " + address + " on " + iface +
+                    " flags " + address.getFlags() + " scope " + address.getScope());
+        }
+    }
+
+    private void maybeLog(String operation, Object o) {
+        if (DBG) {
+            Log.d(TAG, operation + ": " + o.toString());
+        }
+    }
+
+    @Override
+    public void addressUpdated(String iface, LinkAddress address) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("addressUpdated", iface, address);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.addLinkAddress(address);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void addressRemoved(String iface, LinkAddress address) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("addressRemoved", iface, address);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.removeLinkAddress(address);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void routeUpdated(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeUpdated", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.addRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void routeRemoved(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeRemoved", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.removeRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void interfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
+            boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+            if (changed) {
+                synchronized (this) {
+                    mDnsServerRepository.setDnsServersOn(mLinkProperties);
+                }
+                mCallback.update();
+            }
+        }
+    }
+
+    /**
+     * Returns a copy of this object's LinkProperties.
+     */
+    public synchronized LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
+    }
+
+    public synchronized void clearLinkProperties() {
+        // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
+        // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
+        // mLinkProperties, as desired.
+        mDnsServerRepository = new DnsServerRepository();
+        mLinkProperties.clear();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+}
+
+/**
+ * Represents a DNS server entry with an expiry time.
+ *
+ * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
+ * The ordering of entries with the same lifetime is unspecified, because given two servers with
+ * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
+ * faster than comparing the IP address as well.
+ *
+ * Note: this class has a natural ordering that is inconsistent with equals.
+ */
+class DnsServerEntry implements Comparable<DnsServerEntry> {
+    /** The IP address of the DNS server. */
+    public final InetAddress address;
+    /** The time until which the DNS server may be used. A Java millisecond time as might be
+      * returned by currentTimeMillis(). */
+    public long expiry;
+
+    public DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
+        this.address = address;
+        this.expiry = expiry;
+    }
+
+    public int compareTo(DnsServerEntry other) {
+        return Long.compare(other.expiry, this.expiry);
+    }
+}
+
+/**
+ * Tracks DNS server updates received from Netlink.
+ *
+ * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
+ * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be used
+ * any more. In this way, the network can gracefully migrate clients from one set of DNS servers to
+ * another. Announcements can both raise and lower the lifetime, and an announcement can expire
+ * servers by announcing them with a lifetime of zero.
+ *
+ * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of DNS
+ * servers at any given time. These are referred to as the current servers. In case all the
+ * current servers expire, the class also keeps track of a larger (but limited) number of servers
+ * that are promoted to current servers when the current ones expire. In order to minimize updates
+ * to the rest of the system (and potentially expensive cache flushes) this class attempts to keep
+ * the list of current servers constant where possible. More specifically, the list of current
+ * servers is only updated if a new server is learned and there are not yet {@code
+ * NUM_CURRENT_SERVERS} current servers, or if one or more of the current servers expires or is
+ * pushed out of the set. Therefore, the current servers will not necessarily be the ones with the
+ * highest lifetime, but the ones learned first.
+ *
+ * This is by design: if instead the class always preferred the servers with the highest lifetime, a
+ * (misconfigured?) network where two or more routers announce more than {@code NUM_CURRENT_SERVERS}
+ * unique servers would cause persistent oscillations.
+ *
+ * TODO: Currently servers are only expired when a new DNS update is received.
+ * Update them using timers, or possibly on every notification received by NetlinkTracker.
+ *
+ * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
+ * notifications are sent by multiple threads. If future threads use alarms to expire, those
+ * alarms must also be synchronized(this).
+ *
+ */
+class DnsServerRepository {
+
+    /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
+    public static final int NUM_CURRENT_SERVERS = 3;
+
+    /** How many DNS servers we'll keep track of, in total. */
+    public static final int NUM_SERVERS = 12;
+
+    /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
+    private Set<InetAddress> mCurrentServers;
+
+    public static final String TAG = "DnsServerRepository";
+
+    /**
+     * Stores all the DNS servers we know about, for use when the current servers expire.
+     * Always sorted in order of decreasing expiry. The elements in this list are also the values
+     * of mIndex, and may be elements in mCurrentServers.
+     */
+    private ArrayList<DnsServerEntry> mAllServers;
+
+    /**
+     * Indexes the servers so we can update their lifetimes more quickly in the common case where
+     * servers are not being added, but only being refreshed.
+     */
+    private HashMap<InetAddress, DnsServerEntry> mIndex;
+
+    public DnsServerRepository() {
+        mCurrentServers = new HashSet();
+        mAllServers = new ArrayList<DnsServerEntry>(NUM_SERVERS);
+        mIndex = new HashMap<InetAddress, DnsServerEntry>(NUM_SERVERS);
+    }
+
+    /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
+    public synchronized void setDnsServersOn(LinkProperties lp) {
+        lp.setDnsServers(mCurrentServers);
+    }
+
+    /**
+     * Notifies the class of new DNS server information.
+     * @param lifetime the time in seconds that the DNS servers are valid.
+     * @param addresses the string representations of the IP addresses of the DNS servers to use.
+     */
+    public synchronized boolean addServers(long lifetime, String[] addresses) {
+        // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
+        // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
+        // (136 years) is close enough.
+        long now = System.currentTimeMillis();
+        long expiry = now + 1000 * lifetime;
+
+        // Go through the list of servers. For each one, update the entry if one exists, and
+        // create one if it doesn't.
+        for (String addressString : addresses) {
+            InetAddress address;
+            try {
+                address = InetAddress.parseNumericAddress(addressString);
+            } catch (IllegalArgumentException ex) {
+                continue;
+            }
+
+            if (!updateExistingEntry(address, expiry)) {
+                // There was no entry for this server. Create one, unless it's already expired
+                // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
+                if (expiry > now) {
+                    DnsServerEntry entry = new DnsServerEntry(address, expiry);
+                    mAllServers.add(entry);
+                    mIndex.put(address, entry);
+                }
+            }
+        }
+
+        // Sort the servers by expiry.
+        Collections.sort(mAllServers);
+
+        // Prune excess entries and update the current server list.
+        return updateCurrentServers();
+    }
+
+    private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
+        DnsServerEntry existing = mIndex.get(address);
+        if (existing != null) {
+            existing.expiry = expiry;
+            return true;
+        }
+        return false;
+    }
+
+    private synchronized boolean updateCurrentServers() {
+        long now = System.currentTimeMillis();
+        boolean changed = false;
+
+        // Prune excess or expired entries.
+        for (int i = mAllServers.size() - 1; i >= 0; i--) {
+            if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) {
+                DnsServerEntry removed = mAllServers.remove(i);
+                mIndex.remove(removed.address);
+                changed |= mCurrentServers.remove(removed.address);
+            } else {
+                break;
+            }
+        }
+
+        // Add servers to the current set, in order of decreasing lifetime, until it has enough.
+        // Prefer existing servers over new servers in order to minimize updates to the rest of the
+        // system and avoid persistent oscillations.
+        for (DnsServerEntry entry : mAllServers) {
+            if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
+                changed |= mCurrentServers.add(entry.address);
+            } else {
+                break;
+            }
+        }
+        return changed;
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f446c3a..f287fca 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_Picture.cpp \
 	android/graphics/AutoDecodeCancel.cpp \
 	android/graphics/Bitmap.cpp \
 	android/graphics/BitmapFactory.cpp \
@@ -105,6 +105,8 @@
 	android/graphics/Interpolator.cpp \
 	android/graphics/MaskFilter.cpp \
 	android/graphics/Matrix.cpp \
+	android/graphics/MinikinSkia.cpp \
+	android/graphics/MinikinUtils.cpp \
 	android/graphics/Movie.cpp \
 	android/graphics/NinePatch.cpp \
 	android/graphics/NinePatchImpl.cpp \
@@ -120,8 +122,6 @@
 	android/graphics/Region.cpp \
 	android/graphics/Shader.cpp \
 	android/graphics/SurfaceTexture.cpp \
-	android/graphics/TextLayout.cpp \
-	android/graphics/TextLayoutCache.cpp \
 	android/graphics/Typeface.cpp \
 	android/graphics/TypefaceImpl.cpp \
 	android/graphics/Utils.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 \
@@ -196,6 +197,9 @@
 	frameworks/opt/emoji \
 	libcore/include \
 	$(call include-path-for, audio-utils) \
+	frameworks/minikin/include \
+	external/freetype/include
+# TODO: clean up Minikin so it doesn't need the freetype include
 
 LOCAL_SHARED_LIBRARIES := \
 	libmemtrack \
@@ -236,22 +240,14 @@
 	libpdfium \
 	libimg_utils \
 	libnetd_client \
+	libsoundtrigger \
+	libminikin \
+	libstlport
 
 ifeq ($(USE_OPENGL_RENDERER),true)
 	LOCAL_SHARED_LIBRARIES += libhwui
 endif
 
-ifeq ($(USE_MINIKIN), true)
-	LOCAL_CFLAGS += -DUSE_MINIKIN
-	LOCAL_C_INCLUDES += frameworks/minikin/include \
-		external/freetype/include
-	LOCAL_SRC_FILES += 	android/graphics/MinikinSkia.cpp \
-		android/graphics/MinikinUtils.cpp
-# note: the freetype include is spurious; minikin itself probably
-# shouldn't depend on it
-	LOCAL_SHARED_LIBRARIES += libminikin libstlport
-endif
-
 LOCAL_SHARED_LIBRARIES += \
 	libdl
 # we need to access the private Bionic header
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index e069876..598d6c1 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);
@@ -160,6 +159,7 @@
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_AndroidBidi(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
+extern int register_android_server_fingerprint_FingerprintService(JNIEnv* env);
 extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
 extern int register_android_server_Watchdog(JNIEnv* env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -245,14 +245,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 +491,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 +816,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 +1242,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 +1317,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),
@@ -1301,6 +1329,7 @@
     REG_JNI(register_android_media_ToneGenerator),
 
     REG_JNI(register_android_opengl_classes),
+	REG_JNI(register_android_server_fingerprint_FingerprintService),
     REG_JNI(register_android_server_NetworkManagementSocketTagger),
     REG_JNI(register_android_server_Watchdog),
     REG_JNI(register_android_ddm_DdmHandleNativeHeap),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 0328517..c139c9d 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -258,16 +258,16 @@
 
 // can return NULL
 static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) {
-    switch (src.config()) {
-        case SkBitmap::kARGB_8888_Config:
+    switch (src.colorType()) {
+        case kN32_SkColorType:
             if (src.isOpaque()) return ToColor_S32_Opaque;
             return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw;
-        case SkBitmap::kARGB_4444_Config:
+        case kARGB_4444_SkColorType:
             if (src.isOpaque()) return ToColor_S4444_Opaque;
             return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw;
-        case SkBitmap::kRGB_565_Config:
+        case kRGB_565_SkColorType:
             return ToColor_S565;
-        case SkBitmap::kIndex8_Config:
+        case kIndex_8_SkColorType:
             if (src.getColorTable() == NULL) {
                 return NULL;
             }
@@ -291,7 +291,7 @@
 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                               jint offset, jint stride, jint width, jint height,
                               jint configHandle, jboolean isMutable) {
-    SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle);
+    SkColorType colorType = SkBitmapConfigToColorType(static_cast<SkBitmap::Config>(configHandle));
     if (NULL != jColors) {
         size_t n = env->GetArrayLength(jColors);
         if (n < SkAbs32(stride) * (size_t)height) {
@@ -301,12 +301,12 @@
     }
 
     // ARGB_4444 is a deprecated format, convert automatically to 8888
-    if (config == SkBitmap::kARGB_4444_Config) {
-        config = SkBitmap::kARGB_8888_Config;
+    if (colorType == kARGB_4444_SkColorType) {
+        colorType = kN32_SkColorType;
     }
 
     SkBitmap bitmap;
-    bitmap.setConfig(config, width, height);
+    bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));
 
     jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
     if (NULL == buff) {
@@ -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
@@ -489,28 +515,29 @@
 
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
-    const bool              isMutable = p->readInt32() != 0;
-    const SkBitmap::Config  config = (SkBitmap::Config)p->readInt32();
-    const int               width = p->readInt32();
-    const int               height = p->readInt32();
-    const int               rowBytes = p->readInt32();
-    const int               density = p->readInt32();
+    const bool        isMutable = p->readInt32() != 0;
+    const SkColorType colorType = (SkColorType)p->readInt32();
+    const SkAlphaType alphaType = (SkAlphaType)p->readInt32();
+    const int         width = p->readInt32();
+    const int         height = p->readInt32();
+    const int         rowBytes = p->readInt32();
+    const int         density = p->readInt32();
 
-    if (SkBitmap::kARGB_8888_Config != config &&
-            SkBitmap::kRGB_565_Config != config &&
-            SkBitmap::kARGB_4444_Config != config &&
-            SkBitmap::kIndex8_Config != config &&
-            SkBitmap::kA8_Config != config) {
-        SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config);
+    if (kN32_SkColorType != colorType &&
+            kRGB_565_SkColorType != colorType &&
+            kARGB_4444_SkColorType != colorType &&
+            kIndex_8_SkColorType != colorType &&
+            kAlpha_8_SkColorType != colorType) {
+        SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType);
         return NULL;
     }
 
     SkBitmap* bitmap = new SkBitmap;
 
-    bitmap->setConfig(config, width, height, rowBytes);
+    bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes);
 
     SkColorTable* ctable = NULL;
-    if (config == SkBitmap::kIndex8_Config) {
+    if (colorType == kIndex_8_SkColorType) {
         int count = p->readInt32();
         if (count > 0) {
             size_t size = count * sizeof(SkPMColor);
@@ -561,13 +588,14 @@
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
     p->writeInt32(isMutable);
-    p->writeInt32(bitmap->config());
+    p->writeInt32(bitmap->colorType());
+    p->writeInt32(bitmap->alphaType());
     p->writeInt32(bitmap->width());
     p->writeInt32(bitmap->height());
     p->writeInt32(bitmap->rowBytes());
     p->writeInt32(density);
 
-    if (bitmap->config() == SkBitmap::kIndex8_Config) {
+    if (bitmap->colorType() == kIndex_8_SkColorType) {
         SkColorTable* ctable = bitmap->getColorTable();
         if (ctable != NULL) {
             int count = ctable->count();
@@ -799,7 +827,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..86ed677 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;
     }
 
@@ -428,7 +386,7 @@
         // FIXME: If the alphaType is kUnpremul and the image has alpha, the
         // colors may not be correct, since Skia does not yet support drawing
         // to/from unpremultiplied bitmaps.
-        outputBitmap->setConfig(SkImageInfo::Make(scaledWidth, scaledHeight,
+        outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                 colorType, decodingBitmap.alphaType()));
         if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
             return nullObjectReturn("allocation failed for scaled bitmap");
@@ -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..3f323ab 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -19,22 +19,20 @@
 #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
 #include <minikin/Layout.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
-#endif
 
-#include "TextLayout.h"
-#include "TextLayoutCache.h"
 #include "TypefaceImpl.h"
 
 #include "unicode/ubidi.h"
@@ -42,21 +40,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 +60,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 +217,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,157 +255,152 @@
         }
     }
 
+    // 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();
     }
 
     static void freeTextLayoutCaches(JNIEnv* env, jobject) {
-        TextLayoutEngine::getInstance().purgeCaches();
+        Layout::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 +409,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 +429,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 +460,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 +469,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,86 +525,74 @@
             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);
+    static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jlong paintHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect oval;
-        GraphicsJNI::jrectf_to_rect(env, joval, &oval);
+        SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
         canvas->drawOval(oval, *paint);
     }
 
     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);
     }
 
-    static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jobject joval,
-                        jfloat startAngle, jfloat sweepAngle,
-                        jboolean useCenter, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+    static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, jboolean useCenter,
+            jlong paintHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect oval;
-        GraphicsJNI::jrectf_to_rect(env, joval, &oval);
+        SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
         canvas->drawArc(oval, startAngle, sweepAngle, useCenter, *paint);
     }
 
     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 +600,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 +611,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 +671,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 +684,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,14 +696,14 @@
     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);
+        SkImageInfo info = SkImageInfo::Make(width, height,
+                               hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+                               kPremul_SkAlphaType);
         SkBitmap    bitmap;
-        bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config :
-                         SkBitmap::kRGB_565_Config, width, height);
-        if (!bitmap.allocPixels()) {
+        if (!bitmap.allocPixels(info)) {
             return;
         }
 
@@ -638,7 +718,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 +729,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 +839,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);
 
@@ -797,30 +877,29 @@
 
     static void drawText___CIIFFIPaintTypeface(JNIEnv* env, jobject, jlong canvasHandle,
                                                jcharArray text, jint index, jint count,
-                                               jfloat x, jfloat y, jint flags,
+                                               jfloat x, jfloat y, jint bidiFlags,
                                                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);
-        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint, typeface);
+        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, bidiFlags, paint, typeface);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
 
     static void drawText__StringIIFFIPaintTypeface(JNIEnv* env, jobject,
                                                    jlong canvasHandle, jstring text,
                                                    jint start, jint end,
-                                                   jfloat x, jfloat y, jint flags,
+                                                   jfloat x, jfloat y, jint bidiFlags,
                                                    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);
-        drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint, typeface);
+        drawTextWithGlyphs(canvas, textArray, start, end, x, y, bidiFlags, paint, typeface);
         env->ReleaseStringChars(text, textArray);
     }
 
-#ifdef USE_MINIKIN
     class DrawTextFunctor {
     public:
         DrawTextFunctor(const Layout& layout, SkCanvas* canvas, jfloat x, jfloat y, SkPaint* paint,
@@ -862,42 +941,24 @@
         delete[] glyphs;
         delete[] pos;
     }
-#endif
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int end,
-            jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) {
+            jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
 
         jint count = end - start;
-        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, flags, paint, typeface);
+        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, bidiFlags, paint,
+                typeface);
     }
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int count, int contextCount,
-            jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) {
+            jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
 
-#ifdef USE_MINIKIN
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(textArray, start, count, contextCount, css);
         drawGlyphsToSkia(canvas, paint, layout, x, y);
-#else
-        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-                textArray, start, count, contextCount, flags);
-        if (value == NULL) {
-            return;
-        }
-        SkPaint::Align align = paint->getTextAlign();
-        if (align == SkPaint::kCenter_Align) {
-            x -= 0.5 * value->getTotalAdvance();
-        } else if (align == SkPaint::kRight_Align) {
-            x -= value->getTotalAdvance();
-        }
-        paint->setTextAlign(SkPaint::kLeft_Align);
-        doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint);
-        doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint);
-        paint->setTextAlign(align);
-#endif
     }
 
 // Same values used by Skia
@@ -905,42 +966,37 @@
 #define kStdUnderline_Offset    (1.0f / 9.0f)
 #define kStdUnderline_Thickness (1.0f / 18.0f)
 
-static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length, SkPaint* paint) {
-    uint32_t flags;
-    SkDrawFilter* drawFilter = canvas->getDrawFilter();
-    if (drawFilter) {
-        SkPaint paintCopy(*paint);
-        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
-        flags = paintCopy.getFlags();
-    } else {
-        flags = paint->getFlags();
-    }
-    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
-        SkScalar left = x;
-        SkScalar right = x + length;
-        float textSize = paint->getTextSize();
-        float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
-        if (flags & SkPaint::kUnderlineText_Flag) {
-            SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
-            canvas->drawRectCoords(left, top, right, bottom, *paint);
+    static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length,
+            SkPaint* paint) {
+        uint32_t flags;
+        SkDrawFilter* drawFilter = canvas->getDrawFilter();
+        if (drawFilter) {
+            SkPaint paintCopy(*paint);
+            drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+            flags = paintCopy.getFlags();
+        } else {
+            flags = paint->getFlags();
         }
-        if (flags & SkPaint::kStrikeThruText_Flag) {
-            SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
-            canvas->drawRectCoords(left, top, right, bottom, *paint);
+        if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+            SkScalar left = x;
+            SkScalar right = x + length;
+            float textSize = paint->getTextSize();
+            float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+            if (flags & SkPaint::kUnderlineText_Flag) {
+                SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+                SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+                canvas->drawRectCoords(left, top, right, bottom, *paint);
+            }
+            if (flags & SkPaint::kStrikeThruText_Flag) {
+                SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+                SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+                canvas->drawRectCoords(left, top, right, bottom, *paint);
+            }
         }
     }
-}
-
-    static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count,
-            jfloat x, jfloat y, int flags, SkPaint* paint) {
-        // Beware: this needs Glyph encoding (already done on the Paint constructor)
-        canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint);
-    }
 
     static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
-            int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) {
+            int index, int count, jfloat x, jfloat y, SkPaint* paint) {
         SkPoint* posPtr = new SkPoint[count];
         for (int indx = 0; indx < count; indx++) {
             posPtr[indx].fX = x + posArray[indx * 2];
@@ -950,40 +1006,42 @@
         delete[] posPtr;
     }
 
-    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);
+    static void drawTextRun___CIIIIFFZPaintTypeface(
+            JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
+            jint count, jint contextIndex, jint contextCount,
+            jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
 
+        int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
         jchar* chars = env->GetCharArrayElements(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
-                count, contextCount, x, y, dirFlags, paint, typeface);
+                count, contextCount, x, y, bidiFlags, paint, typeface);
         env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
 
-    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);
+    static void drawTextRun__StringIIIIFFZPaintTypeface(
+            JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start,
+            jint end, jint contextStart, jint contextEnd,
+            jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
 
+        int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
         jint count = end - start;
         jint contextCount = contextEnd - contextStart;
         const jchar* chars = env->GetStringChars(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
-                count, contextCount, x, y, dirFlags, paint, typeface);
+                count, contextCount, x, y, bidiFlags, paint, typeface);
         env->ReleaseStringChars(text, chars);
     }
 
     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 +1072,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;
@@ -1041,29 +1099,76 @@
         delete[] posPtr;
     }
 
+    class DrawTextOnPathFunctor {
+    public:
+        DrawTextOnPathFunctor(const Layout& layout, SkCanvas* canvas, float hOffset,
+                    float vOffset, SkPaint* paint, SkPath* path)
+                : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset),
+                    paint(paint), path(path) {
+        }
+        void operator()(size_t start, size_t end) {
+            uint16_t glyphs[1];
+            for (size_t i = start; i < end; i++) {
+                glyphs[0] = layout.getGlyphId(i);
+                float x = hOffset + layout.getX(i);
+                float y = vOffset + layout.getY(i);
+                canvas->drawTextOnPathHV(glyphs, sizeof(glyphs), *path, x, y, *paint);
+            }
+        }
+    private:
+        const Layout& layout;
+        SkCanvas* canvas;
+        float hOffset;
+        float vOffset;
+        SkPaint* paint;
+        SkPath* path;
+    };
+
+    static void doDrawTextOnPath(SkPaint* paint, const jchar* text, int count, int bidiFlags,
+            float hOffset, float vOffset, SkPath* path, SkCanvas* canvas, TypefaceImpl* typeface) {
+        Layout layout;
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(text, 0, count, count, css);
+        hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path);
+        // Set align to left for drawing, as we don't want individual
+        // glyphs centered or right-aligned; the offset above takes
+        // care of all alignment.
+        SkPaint::Align align = paint->getTextAlign();
+        paint->setTextAlign(SkPaint::kLeft_Align);
+
+        DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paint, path);
+        MinikinUtils::forFontRun(layout, paint, f);
+        paint->setTextAlign(align);
+    }
+
     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);
+            jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle,
+            jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
 
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset,
-                                   path, canvas);
+        doDrawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas, typeface);
         env->ReleaseCharArrayElements(text, textArray, 0);
     }
 
     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);
+            jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle,
+            jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
         const jchar* text_ = env->GetStringChars(text, NULL);
         int count = env->GetStringLength(text);
-        TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
-                                   path, canvas);
+        doDrawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas, typeface);
         env->ReleaseStringChars(text, text_);
     }
 
@@ -1096,7 +1201,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 +1217,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 +1225,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 +1250,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,21 +1257,14 @@
     {"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},
+    {"native_drawOval","(JFFFFJ)V", (void*) SkCanvasGlue::drawOval},
     {"native_drawCircle","(JFFFJ)V", (void*) SkCanvasGlue::drawCircle},
-    {"native_drawArc","(JLandroid/graphics/RectF;FFZJ)V",
-        (void*) SkCanvasGlue::drawArc},
+    {"native_drawArc","(JFFFFFFZJ)V", (void*) SkCanvasGlue::drawArc},
     {"native_drawRoundRect","(JFFFFFFJ)V",
         (void*) SkCanvasGlue::drawRoundRect},
     {"native_drawPath","(JJJ)V", (void*) SkCanvasGlue::drawPath},
@@ -1201,17 +1286,13 @@
         (void*) SkCanvasGlue::drawText___CIIFFIPaintTypeface},
     {"native_drawText","(JLjava/lang/String;IIFFIJJ)V",
         (void*) SkCanvasGlue::drawText__StringIIFFIPaintTypeface},
-    {"native_drawTextRun","(J[CIIIIFFIJJ)V",
-        (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaintTypeface},
-    {"native_drawTextRun","(JLjava/lang/String;IIIIFFIJJ)V",
-        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaintTypeface},
-    {"native_drawPosText","(J[CII[FJ)V",
-        (void*) SkCanvasGlue::drawPosText___CII_FPaint},
-    {"native_drawPosText","(JLjava/lang/String;[FJ)V",
-        (void*) SkCanvasGlue::drawPosText__String_FPaint},
-    {"native_drawTextOnPath","(J[CIIJFFIJ)V",
+    {"native_drawTextRun","(J[CIIIIFFZJJ)V",
+        (void*) SkCanvasGlue::drawTextRun___CIIIIFFZPaintTypeface},
+    {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V",
+        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFZPaintTypeface},
+    {"native_drawTextOnPath","(J[CIIJFFIJJ)V",
         (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
-    {"native_drawTextOnPath","(JLjava/lang/String;JFFIJ)V",
+    {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V",
         (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
 
     {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches},
@@ -1236,4 +1317,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/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index dd6b36f..1d465b3 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -27,44 +27,34 @@
 #include <androidfw/AssetManager.h>
 #include "Utils.h"
 
-#ifdef USE_MINIKIN
+#include "TypefaceImpl.h"
 #include <minikin/FontFamily.h>
 #include "MinikinSkia.h"
-#endif
 
 namespace android {
 
 static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
-#ifdef USE_MINIKIN
     FontLanguage fontLanguage;
     if (lang != NULL) {
         ScopedUtfChars str(env, lang);
         fontLanguage = FontLanguage(str.c_str(), str.size());
     }
     return (jlong)new FontFamily(fontLanguage, variant);
-#else
-    return 0;
-#endif
 }
 
 static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
-#ifdef USE_MINIKIN
     FontFamily* fontFamily = reinterpret_cast<FontFamily*>(familyPtr);
     fontFamily->Unref();
-#endif
 }
 
-#ifdef USE_MINIKIN
 static jboolean addSkTypeface(FontFamily* family, SkTypeface* face) {
     MinikinFont* minikinFont = new MinikinFontSkia(face);
     bool result = family->addFont(minikinFont);
     minikinFont->Unref();
     return result;
 }
-#endif
 
 static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jstring path) {
-#ifdef USE_MINIKIN
     NPE_CHECK_RETURN_ZERO(env, path);
     ScopedUtfChars str(env, path);
     SkTypeface* face = SkTypeface::CreateFromFile(str.c_str());
@@ -74,14 +64,10 @@
     }
     FontFamily* fontFamily = (FontFamily*)familyPtr;
     return addSkTypeface(fontFamily, face);
-#else
-    return false;
-#endif
 }
 
 static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPtr,
         jobject jassetMgr, jstring jpath) {
-#ifdef USE_MINIKIN
     NPE_CHECK_RETURN_ZERO(env, jassetMgr);
     NPE_CHECK_RETURN_ZERO(env, jpath);
 
@@ -108,9 +94,6 @@
     }
     FontFamily* fontFamily = (FontFamily*)familyPtr;
     return addSkTypeface(fontFamily, face);
-#else
-    return false;
-#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index dce185d..2bd7a28 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -320,7 +320,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,17 +345,6 @@
     return p;
 }
 
-SkPicture* 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);
-    SkASSERT(p);
-    return p;
-}
-
 SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
 {
     SkASSERT(env);
@@ -698,7 +687,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..7fda3d9 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -8,13 +8,15 @@
 #include "SkPoint.h"
 #include "SkRect.h"
 #include "SkImageDecoder.h"
-#include "TypefaceImpl.h"
 #include <jni.h>
 
 class SkBitmapRegionDecoder;
 class SkCanvas;
 class SkPaint;
-class SkPicture;
+
+namespace android {
+class TypefaceImpl;
+}
 
 class GraphicsJNI {
 public:
@@ -45,11 +47,11 @@
     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 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/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index a9360ea..f02f118 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "SkPaint.h"
+#include "SkPathMeasure.h"
 #include "minikin/Layout.h"
 #include "TypefaceImpl.h"
 #include "MinikinSkia.h"
@@ -58,7 +59,6 @@
     SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
     const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact";
     off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr);
-    layout->setProperties(css);
     return std::string(css);
 }
 
@@ -76,4 +76,20 @@
     return 0;
 }
 
+float MinikinUtils::hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path) {
+    float align = 0;
+    switch (paint->getTextAlign()) {
+        case SkPaint::kCenter_Align:
+            align = -0.5f;
+            break;
+        case SkPaint::kRight_Align:
+            align = -1;
+            break;
+        default:
+            return 0;
+    }
+    SkPathMeasure measure(path, false);
+    return align * (layout.getAdvance() - measure.getLength());
+}
+
 }
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index a96c6b1..1663860 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -26,6 +26,18 @@
 
 namespace android {
 
+// TODO: these should be defined in Minikin's Layout.h
+enum {
+    kBidi_LTR = 0,
+    kBidi_RTL = 1,
+    kBidi_Default_LTR = 2,
+    kBidi_Default_RTL = 3,
+    kBidi_Force_LTR = 4,
+    kBidi_Force_RTL = 5,
+
+    kBidi_Mask = 0x7
+};
+
 class Layout;
 class TypefaceImpl;
 
@@ -36,6 +48,7 @@
 
     static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout);
 
+    static float hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path);
     // f is a functor of type void f(size_t start, size_t end);
     template <typename F>
     static void forFontRun(const Layout& layout, SkPaint* paint, F& f) {
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/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 8b11d31..0ad390a 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -34,13 +34,12 @@
 #include "unicode/uloc.h"
 #include "unicode/ushape.h"
 #include "utils/Blur.h"
-#include "TextLayout.h"
 
-#ifdef USE_MINIKIN
+#include <minikin/GraphemeBreak.h>
 #include <minikin/Layout.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
-#endif
+#include "TypefaceImpl.h"
 
 // temporary for debugging
 #include <Caches.h>
@@ -303,14 +302,8 @@
     }
 
     static jlong setTypeface(JNIEnv* env, jobject clazz, jlong objHandle, jlong typefaceHandle) {
-#ifndef USE_MINIKIN
-        SkPaint* obj = reinterpret_cast<SkPaint*>(objHandle);
-        SkTypeface* typeface = reinterpret_cast<SkTypeface*>(typefaceHandle);
-        return reinterpret_cast<jlong>(obj->setTypeface(typeface));
-#else
-        // TODO(raph): not yet implemented
+        // TODO: in Paint refactoring, set typeface on android Paint, not SkPaint
         return NULL;
-#endif
     }
 
     static jlong setRasterizer(JNIEnv* env, jobject clazz, jlong objHandle, jlong rasterizerHandle) {
@@ -436,22 +429,18 @@
         const int kElegantDescent = -500;
         const int kElegantLeading = 0;
         SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
-#ifdef USE_MINIKIN
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
         typeface = TypefaceImpl_resolveDefault(typeface);
         FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
         float saveSkewX = paint->getTextSkewX();
         bool savefakeBold = paint->isFakeBoldText();
         MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
-#endif
         SkScalar spacing = paint->getFontMetrics(metrics);
-#ifdef USE_MINIKIN
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
         // restore the original settings.
         paint->setTextSkewX(saveSkewX);
         paint->setFakeBoldText(savefakeBold);
-#endif
         SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
         if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
             SkScalar size = paint->getTextSize();
@@ -533,17 +522,11 @@
         const jchar* textArray = env->GetCharArrayElements(text, NULL);
         jfloat result = 0;
 
-#ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
         std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(textArray, index, count, textLength, css);
         result = layout.getAdvance();
-#else
-        TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
-                bidiFlags, NULL /* dont need all advances */, &result);
-#endif
-
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
         return result;
     }
@@ -567,16 +550,11 @@
         SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
         jfloat width = 0;
 
-#ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
         std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(textArray, start, count, textLength, css);
         width = layout.getAdvance();
-#else
-        TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
-                bidiFlags, NULL /* dont need all advances */, &width);
-#endif
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -595,16 +573,11 @@
         SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
         jfloat width = 0;
 
-#ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
         std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(textArray, 0, textLength, textLength, css);
         width = layout.getAdvance();
-#else
-        TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
-                bidiFlags, NULL /* dont need all advances */, &width);
-#endif
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -631,15 +604,10 @@
         AutoJavaFloatArray autoWidths(env, widths, count);
         jfloat* widthsArray = autoWidths.ptr();
 
-#ifdef USE_MINIKIN
         Layout layout;
         std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(text, 0, count, count, css);
         layout.getAdvances(widthsArray);
-#else
-        TextLayout::getTextRunAdvances(paint, text, 0, count, count,
-                bidiFlags, widthsArray, NULL /* dont need totalAdvance */);
-#endif
 
         return count;
     }
@@ -665,49 +633,8 @@
         return count;
     }
 
-    static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count,
-            jint contextCount, jint flags, jcharArray glyphs) {
-        NPE_CHECK_RETURN_ZERO(env, paint);
-        NPE_CHECK_RETURN_ZERO(env, text);
-
-        if ((start | count | contextCount) < 0 || contextCount < count || !glyphs) {
-            doThrowAIOOBE(env);
-            return 0;
-        }
-        if (count == 0) {
-            return 0;
-        }
-        size_t glypthsLength = env->GetArrayLength(glyphs);
-        if ((size_t)count > glypthsLength) {
-            doThrowAIOOBE(env);
-            return 0;
-        }
-
-        jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
-
-        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-                text, start, count, contextCount, flags);
-        const jchar* shapedGlyphs = value->getGlyphs();
-        size_t glyphsCount = value->getGlyphsCount();
-        memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount);
-
-        env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT);
-        return glyphsCount;
-    }
-
-    static jint getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, jlong paintHandle,
-            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
-            jcharArray glyphs) {
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        const jchar* textArray = env->GetStringChars(text, NULL);
-        int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart,
-                end - start, contextEnd - contextStart, flags, glyphs);
-        env->ReleaseStringChars(text, textArray);
-        return count;
-    }
-
     static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, TypefaceImpl* typeface, const jchar *text,
-                                    jint start, jint count, jint contextCount, jint flags,
+                                    jint start, jint count, jint contextCount, jboolean isRtl,
                                     jfloatArray advances, jint advancesIndex) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         NPE_CHECK_RETURN_ZERO(env, text);
@@ -729,16 +656,13 @@
         jfloat* advancesArray = new jfloat[count];
         jfloat totalAdvance = 0;
 
-#ifdef USE_MINIKIN
+        int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+
         Layout layout;
-        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(text, start, count, contextCount, css);
         layout.getAdvances(advancesArray);
         totalAdvance = layout.getAdvance();
-#else
-        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
-                                       advancesArray, &totalAdvance);
-#endif
 
         if (advances != NULL) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
@@ -747,28 +671,28 @@
         return totalAdvance;
     }
 
-    static jfloat getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
+    static jfloat getTextRunAdvances___CIIIIZ_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
             jlong typefaceHandle,
             jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
-            jint flags, jfloatArray advances, jint advancesIndex) {
+            jboolean isRtl, jfloatArray advances, jint advancesIndex) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         jchar* textArray = env->GetCharArrayElements(text, NULL);
         jfloat result = doTextRunAdvances(env, paint, typeface, textArray + contextIndex,
-                index - contextIndex, count, contextCount, flags, advances, advancesIndex);
+                index - contextIndex, count, contextCount, isRtl, advances, advancesIndex);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
-    static jfloat getTextRunAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
+    static jfloat getTextRunAdvances__StringIIIIZ_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
             jlong typefaceHandle,
-            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl,
             jfloatArray advances, jint advancesIndex) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         const jchar* textArray = env->GetStringChars(text, NULL);
         jfloat result = doTextRunAdvances(env, paint, typeface, textArray + contextStart,
-                start - contextStart, end - start, contextEnd - contextStart, flags,
+                start - contextStart, end - start, contextEnd - contextStart, isRtl,
                 advances, advancesIndex);
         env->ReleaseStringChars(text, textArray);
         return result;
@@ -776,69 +700,31 @@
 
     static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
             jint count, jint flags, jint offset, jint opt) {
-        jfloat scalarArray[count];
-
-        TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags,
-                scalarArray, NULL /* dont need totalAdvance */);
-
-        jint pos = offset - start;
-        switch (opt) {
-        case AFTER:
-          if (pos < count) {
-            pos += 1;
-          }
-          // fall through
-        case AT_OR_AFTER:
-          while (pos < count && scalarArray[pos] == 0) {
-            ++pos;
-          }
-          break;
-        case BEFORE:
-          if (pos > 0) {
-            --pos;
-          }
-          // fall through
-        case AT_OR_BEFORE:
-          while (pos > 0 && scalarArray[pos] == 0) {
-            --pos;
-          }
-          break;
-        case AT:
-        default:
-          if (scalarArray[pos] == 0) {
-            pos = -1;
-          }
-          break;
-        }
-
-        if (pos != -1) {
-          pos += start;
-        }
-
-        return pos;
+        GraphemeBreak::MoveOpt moveOpt = GraphemeBreak::MoveOpt(opt);
+        size_t result = GraphemeBreak::getTextRunCursor(text, start, count, offset, moveOpt);
+        return static_cast<jint>(result);
     }
 
     static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text,
-            jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags,
+        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, dir,
                 offset, cursorOpt);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
     static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, jstring text,
-            jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextEnd, jint dir, jint offset, jint cursorOpt) {
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         const jchar* textArray = env->GetStringChars(text, NULL);
         jint result = doTextRunCursor(env, paint, textArray, contextStart,
-                contextEnd - contextStart, flags, offset, cursorOpt);
+                contextEnd - contextStart, dir, offset, cursorOpt);
         env->ReleaseStringChars(text, textArray);
         return result;
     }
 
-#ifdef USE_MINIKIN
     class GetTextFunctor {
     public:
         GetTextFunctor(const Layout& layout, SkPath* path, jfloat x, jfloat y, SkPaint* paint,
@@ -869,11 +755,9 @@
         SkPoint* pos;
         SkPath tmpPath;
     };
-#endif
 
     static void getTextPath(JNIEnv* env, SkPaint* paint, TypefaceImpl* typeface, const jchar* text,
             jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
-#ifdef USE_MINIKIN
         Layout layout;
         std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
         layout.doLayout(text, 0, count, count, css);
@@ -890,9 +774,6 @@
         paint->setTextAlign(align);
         delete[] glyphs;
         delete[] pos;
-#else
-        TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
-#endif
     }
 
     static void getTextPath___C(JNIEnv* env, jobject clazz, jlong paintHandle,
@@ -934,32 +815,48 @@
         return paint->getLooper() && paint->getLooper()->asABlurShadow(NULL);
     }
 
-    static int breakText(JNIEnv* env, SkPaint& paint, const jchar text[],
+    static int breakText(JNIEnv* env, const SkPaint& paint, TypefaceImpl* typeface, const jchar text[],
                          int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured,
-                         SkPaint::TextBufferDirection tbd) {
-        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count, bidiFlags);
-        if (value == NULL) {
-            return 0;
+                         SkPaint::TextBufferDirection textBufferDirection) {
+        size_t measuredCount = 0;
+        float measured = 0;
+
+        Layout layout;
+        std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
+        layout.doLayout(text, 0, count, count, css);
+        float* advances = new float[count];
+        layout.getAdvances(advances);
+        const bool forwardScan = (textBufferDirection == SkPaint::kForward_TextBufferDirection);
+        for (int i = 0; i < count; i++) {
+            // traverse in the given direction
+            int index = forwardScan ? i : (count - i - 1);
+            float width = advances[index];
+            if (measured + width > maxWidth) {
+                break;
+            }
+            // properly handle clusters when scanning backwards
+            if (forwardScan || width != 0.0f) {
+                measuredCount = i + 1;
+            }
+            measured += width;
         }
-        SkScalar     measured;
-        size_t       bytes = paint.breakText(value->getGlyphs(), value->getGlyphsCount() << 1,
-                maxWidth, &measured, tbd);
-        SkASSERT((bytes & 1) == 0);
+        delete[] advances;
 
         if (jmeasured && env->GetArrayLength(jmeasured) > 0) {
             AutoJavaFloatArray autoMeasured(env, jmeasured, 1);
             jfloat* array = autoMeasured.ptr();
-            array[0] = SkScalarToFloat(measured);
+            array[0] = measured;
         }
-        return bytes >> 1;
+        return measuredCount;
     }
 
-    static jint breakTextC(JNIEnv* env, jobject jpaint, jcharArray jtext,
+    static jint breakTextC(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jcharArray jtext,
             jint index, jint count, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) {
-        NPE_CHECK_RETURN_ZERO(env, jpaint);
         NPE_CHECK_RETURN_ZERO(env, jtext);
 
+        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
         SkPaint::TextBufferDirection tbd;
         if (count < 0) {
             tbd = SkPaint::kBackward_TextBufferDirection;
@@ -974,28 +871,28 @@
             return 0;
         }
 
-        SkPaint*     paint = GraphicsJNI::getNativePaint(env, jpaint);
         const jchar* text = env->GetCharArrayElements(jtext, NULL);
-        count = breakText(env, *paint, text + index, count, maxWidth,
+        count = breakText(env, *paint, typeface, text + index, count, maxWidth,
                           bidiFlags, jmeasuredWidth, tbd);
         env->ReleaseCharArrayElements(jtext, const_cast<jchar*>(text),
                                       JNI_ABORT);
         return count;
     }
 
-    static jint breakTextS(JNIEnv* env, jobject jpaint, jstring jtext,
+    static jint breakTextS(JNIEnv* env, jobject clazz, jlong paintHandle, jlong typefaceHandle, jstring jtext,
                 jboolean forwards, jfloat maxWidth, jint bidiFlags, jfloatArray jmeasuredWidth) {
-        NPE_CHECK_RETURN_ZERO(env, jpaint);
         NPE_CHECK_RETURN_ZERO(env, jtext);
 
+        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
         SkPaint::TextBufferDirection tbd = forwards ?
                                         SkPaint::kForward_TextBufferDirection :
                                         SkPaint::kBackward_TextBufferDirection;
 
-        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
         int count = env->GetStringLength(jtext);
         const jchar* text = env->GetStringChars(jtext, NULL);
-        count = breakText(env, *paint, text, count, maxWidth, bidiFlags, jmeasuredWidth, tbd);
+        count = breakText(env, *paint, typeface, text, count, maxWidth, bidiFlags, jmeasuredWidth, tbd);
         env->ReleaseStringChars(jtext, text);
         return count;
     }
@@ -1005,7 +902,6 @@
         SkRect  r;
         SkIRect ir;
 
-#ifdef USE_MINIKIN
         Layout layout;
         std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
         layout.doLayout(text, 0, count, count, css);
@@ -1015,14 +911,6 @@
         r.fTop = rect.mTop;
         r.fRight = rect.mRight;
         r.fBottom = rect.mBottom;
-#else
-        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count, bidiFlags);
-        if (value == NULL) {
-            return;
-        }
-        paint.measureText(value->getGlyphs(), value->getGlyphsCount() << 1, &r);
-#endif
         r.roundOut(&ir);
         GraphicsJNI::irect_to_jrect(ir, env, bounds);
     }
@@ -1106,18 +994,15 @@
     {"native_measureText","([CIII)F", (void*) SkPaintGlue::measureText_CIII},
     {"native_measureText","(Ljava/lang/String;I)F", (void*) SkPaintGlue::measureText_StringI},
     {"native_measureText","(Ljava/lang/String;III)F", (void*) SkPaintGlue::measureText_StringIII},
-    {"native_breakText","([CIIFI[F)I", (void*) SkPaintGlue::breakTextC},
-    {"native_breakText","(Ljava/lang/String;ZFI[F)I", (void*) SkPaintGlue::breakTextS},
+    {"native_breakText","(JJ[CIIFI[F)I", (void*) SkPaintGlue::breakTextC},
+    {"native_breakText","(JJLjava/lang/String;ZFI[F)I", (void*) SkPaintGlue::breakTextS},
     {"native_getTextWidths","(JJ[CIII[F)I", (void*) SkPaintGlue::getTextWidths___CIII_F},
     {"native_getTextWidths","(JJLjava/lang/String;III[F)I", (void*) SkPaintGlue::getTextWidths__StringIII_F},
-    {"native_getTextRunAdvances","(JJ[CIIIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FI},
-    {"native_getTextRunAdvances","(JJLjava/lang/String;IIIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
+    {"native_getTextRunAdvances","(JJ[CIIIIZ[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvances___CIIIIZ_FI},
+    {"native_getTextRunAdvances","(JJLjava/lang/String;IIIIZ[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvances__StringIIIIZ_FI},
 
-
-    {"native_getTextGlyphs","(JLjava/lang/String;IIIII[C)I",
-        (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
     {"native_getTextRunCursor", "(J[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
     {"native_getTextRunCursor", "(JLjava/lang/String;IIIII)I",
         (void*) SkPaintGlue::getTextRunCursor__String},
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index 420a17f..6ef1d2c 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -2,22 +2,22 @@
 **
 ** Copyright 2006, 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 
+** 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 
+**     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 
+** 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.
 */
 
 // This file was generated from the C++ include file: SkPath.h
 // Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
 // or one of the auxilary file specifications in device/tools/gluemaker.
 
 #include "jni.h"
@@ -92,7 +92,7 @@
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         return obj->isEmpty();
     }
- 
+
     static jboolean isRect(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect) {
         SkRect rect;
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -100,65 +100,66 @@
         GraphicsJNI::rect_to_jrectf(rect, env, jrect);
         return result;
     }
- 
+
     static void computeBounds(JNIEnv* env, jobject clazz, jlong objHandle, jobject jbounds) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         const SkRect& bounds = obj->getBounds();
         GraphicsJNI::rect_to_jrectf(bounds, env, jbounds);
     }
- 
+
     static void incReserve(JNIEnv* env, jobject clazz, jlong objHandle, jint extraPtCount) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->incReserve(extraPtCount);
     }
- 
+
     static void moveTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->moveTo(x, y);
     }
- 
+
     static void rMoveTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->rMoveTo(dx, dy);
     }
- 
+
     static void lineTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->lineTo(x, y);
     }
- 
+
     static void rLineTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->rLineTo(dx, dy);
     }
- 
+
     static void quadTo__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->quadTo(x1, y1, x2, y2);
     }
- 
+
     static void rQuadTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->rQuadTo(dx1, dy1, dx2, dy2);
     }
- 
+
     static void cubicTo__FFFFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->cubicTo(x1, y1, x2, y2, x3, y3);
     }
- 
+
     static void rCubicTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->rCubicTo(x1, y1, x2, y2, x3, y3);
     }
- 
-    static void arcTo(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jfloat startAngle, jfloat sweepAngle, jboolean forceMoveTo) {
+
+    static void arcTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle,
+            jboolean forceMoveTo) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
-        SkRect oval_;
-        GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
-        obj->arcTo(oval_, startAngle, sweepAngle, forceMoveTo);
+        SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
+        obj->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
     }
- 
+
     static void close(JNIEnv* env, jobject clazz, jlong objHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->close();
@@ -185,28 +186,26 @@
         obj->addCircle(x, y, radius, dir);
     }
 
-    static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jobject oval, jfloat startAngle, jfloat sweepAngle) {
-        SkRect oval_;
+    static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle) {
+        SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
-        GraphicsJNI::jrectf_to_rect(env, oval, &oval_);
-        obj->addArc(oval_, startAngle, sweepAngle);
+        obj->addArc(oval, startAngle, sweepAngle);
     }
 
-    static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect,
-            jfloat rx, jfloat ry, jint dirHandle) {
-        SkRect rect;
+    static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) {
+        SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
-        GraphicsJNI::jrectf_to_rect(env, jrect, &rect);
         obj->addRoundRect(rect, rx, ry, dir);
     }
-    
-    static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jobject jrect,
-            jfloatArray array, jint dirHandle) {
-        SkRect rect;
+
+    static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jfloat left, jfloat top,
+            jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) {
+        SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle);
-        GraphicsJNI::jrectf_to_rect(env, jrect, &rect);
         AutoJavaFloatArray  afa(env, array, 8);
 #ifdef SK_SCALAR_IS_FLOAT
         const float* src = afa.ptr();
@@ -215,32 +214,32 @@
 #endif
         obj->addRoundRect(rect, src, dir);
     }
-    
+
     static void addPath__PathFF(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jfloat dx, jfloat dy) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
         obj->addPath(*src, dx, dy);
     }
- 
+
     static void addPath__Path(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
         obj->addPath(*src);
     }
- 
+
     static void addPath__PathMatrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jlong matrixHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
         SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         obj->addPath(*src, *matrix);
     }
- 
+
     static void offset__FFPath(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy, jlong dstHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
         obj->offset(dx, dy, dst);
     }
- 
+
     static void offset__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->offset(dx, dy);
@@ -250,14 +249,14 @@
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         obj->setLastPt(dx, dy);
     }
- 
+
     static void transform__MatrixPath(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle, jlong dstHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
         obj->transform(*matrix, dst);
     }
- 
+
     static void transform__Matrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle) {
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
@@ -487,14 +486,14 @@
     {"native_rQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo},
     {"native_cubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF},
     {"native_rCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo},
-    {"native_arcTo","(JLandroid/graphics/RectF;FFZ)V", (void*) SkPathGlue::arcTo},
+    {"native_arcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo},
     {"native_close","(J)V", (void*) SkPathGlue::close},
     {"native_addRect","(JFFFFI)V", (void*) SkPathGlue::addRect},
     {"native_addOval","(JFFFFI)V", (void*) SkPathGlue::addOval},
     {"native_addCircle","(JFFFI)V", (void*) SkPathGlue::addCircle},
-    {"native_addArc","(JLandroid/graphics/RectF;FF)V", (void*) SkPathGlue::addArc},
-    {"native_addRoundRect","(JLandroid/graphics/RectF;FFI)V", (void*) SkPathGlue::addRoundRectXY},
-    {"native_addRoundRect","(JLandroid/graphics/RectF;[FI)V", (void*) SkPathGlue::addRoundRect8},
+    {"native_addArc","(JFFFFFF)V", (void*) SkPathGlue::addArc},
+    {"native_addRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY},
+    {"native_addRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8},
     {"native_addPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF},
     {"native_addPath","(JJ)V", (void*) SkPathGlue::addPath__Path},
     {"native_addPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix},
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index bac8ef7..bc0c25f 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 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.
@@ -14,125 +14,104 @@
  * limitations under the License.
  */
 
-#include "jni.h"
-#include "GraphicsJNI.h"
-#include <android_runtime/AndroidRuntime.h>
+#include "Picture.h"
 
 #include "SkCanvas.h"
-#include "SkPicture.h"
 #include "SkStream.h"
-#include "SkTemplates.h"
-#include "CreateJavaOutputStreamAdaptor.h"
 
 namespace android {
 
-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);
+Picture::Picture(const Picture* 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;
     }
-
-    static jlong deserialize(JNIEnv* env, jobject, jobject jstream,
-                             jbyteArray jstorage) {
-        SkPicture* picture = NULL;
-        SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
-        if (strm) {
-            picture = SkPicture::CreateFromStream(strm);
-            delete strm;
-        }
-        return reinterpret_cast<jlong>(picture);
-    }
-
-    static void killPicture(JNIEnv* env, jobject, jlong pictureHandle) {
-        SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
-        SkASSERT(picture);
-        picture->unref();
-    }
-
-    static void draw(JNIEnv* env, jobject, jlong canvasHandle,
-                            jlong pictureHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkPicture* picture = reinterpret_cast<SkPicture*>(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);
-        SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
-        
-        if (NULL != strm) {
-            picture->serialize(strm);
-            delete strm;
-            return JNI_TRUE;
-        }
-        return JNI_FALSE;
-    }
-
-    static jint getWidth(JNIEnv* env, jobject jpic) {
-        NPE_CHECK_RETURN_ZERO(env, jpic);
-        int width = GraphicsJNI::getNativePicture(env, jpic)->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();
-        return static_cast<jint>(height);
-    }
-
-    static jlong beginRecording(JNIEnv* env, jobject, jlong pictHandle,
-                                    jint w, jint h) {
-        SkPicture* pict = reinterpret_cast<SkPicture*>(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
-        // unref in its finalizer, so we have to ref it here, so that both that
-        // Canvas.java and our picture can both be owners
-        canvas->ref();
-        return reinterpret_cast<jlong>(canvas);
-    }
-
-    static void endRecording(JNIEnv* env, jobject, jlong pictHandle) {
-        SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle);
-        pict->endRecording();
-    }
-};
-
-static JNINativeMethod gPictureMethods[] = {
-    {"getWidth", "()I", (void*) SkPictureGlue::getWidth},
-    {"getHeight", "()I", (void*) SkPictureGlue::getHeight},
-    {"nativeConstructor", "(J)J", (void*) SkPictureGlue::newPicture},
-    {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)SkPictureGlue::deserialize},
-    {"nativeBeginRecording", "(JII)J", (void*) SkPictureGlue::beginRecording},
-    {"nativeEndRecording", "(J)V", (void*) SkPictureGlue::endRecording},
-    {"nativeDraw", "(JJ)V", (void*) SkPictureGlue::draw},
-    {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)SkPictureGlue::serialize},
-    {"nativeDestructor","(J)V", (void*) SkPictureGlue::killPicture}
-};
-
-#include <android_runtime/AndroidRuntime.h>
-
-#define REG(env, name, array) \
-    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
-    SK_ARRAY_COUNT(array));  \
-    if (result < 0) return result
-
-int register_android_graphics_Picture(JNIEnv* env) {
-    int result;
-
-    REG(env, "android/graphics/Picture", gPictureMethods);
-
-    return result;
 }
 
+SkCanvas* Picture::beginRecording(int width, int height) {
+    mPicture.reset(NULL);
+    mRecorder.reset(new SkPictureRecorder);
+    mWidth = width;
+    mHeight = height;
+    return mRecorder->beginRecording(width, height, NULL, 0);
 }
 
+void Picture::endRecording() {
+    if (NULL != mRecorder.get()) {
+        mPicture.reset(mRecorder->endRecording());
+        mRecorder.reset(NULL);
+    }
+}
 
+int Picture::width() const {
+    if (NULL != mPicture.get()) {
+        SkASSERT(mPicture->width() == mWidth);
+        SkASSERT(mPicture->height() == mHeight);
+    }
+
+    return mWidth;
+}
+
+int Picture::height() const {
+    if (NULL != mPicture.get()) {
+        SkASSERT(mPicture->width() == mWidth);
+        SkASSERT(mPicture->height() == mHeight);
+    }
+
+    return mHeight;
+}
+
+Picture* Picture::CreateFromStream(SkStream* stream) {
+    Picture* newPict = new Picture;
+
+    newPict->mPicture.reset(SkPicture::CreateFromStream(stream));
+    if (NULL != newPict->mPicture.get()) {
+        newPict->mWidth = newPict->mPicture->width();
+        newPict->mHeight = newPict->mPicture->height();
+    }
+
+    return newPict;
+}
+
+void Picture::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 Picture::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* Picture::makePartialCopy() const {
+    SkASSERT(NULL != mRecorder.get());
+
+    SkPictureRecorder reRecorder;
+
+    SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0);
+    mRecorder->partialReplay(canvas);
+    return reRecorder.endRecording();
+}
+
+}; // namespace android
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
new file mode 100644
index 0000000..abb0403
--- /dev/null
+++ b/core/jni/android/graphics/Picture.h
@@ -0,0 +1,67 @@
+/*
+ * 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_GRAPHICS_PICTURE_H
+#define ANDROID_GRAPHICS_PICTURE_H
+
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkRefCnt.h"
+#include "SkTemplates.h"
+
+class SkCanvas;
+class SkPicture;
+class SkPictureRecorder;
+class SkStream;
+class SkWStream;
+
+namespace android {
+
+// 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 Picture {
+public:
+    explicit Picture(const Picture* src = NULL);
+
+    SkCanvas* beginRecording(int width, int height);
+
+    void endRecording();
+
+    int width() const;
+
+    int height() const;
+
+    static Picture* 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;
+};
+
+}; // namespace android
+#endif // ANDROID_GRAPHICS_PICTURE_H
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 621534e..eea16f1 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -227,7 +227,7 @@
     }
 }
 
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz,
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
         jint texName, jboolean singleBufferMode, jobject weakThiz)
 {
     sp<IGraphicBufferProducer> producer;
@@ -239,8 +239,15 @@
         consumer->setDefaultMaxBufferCount(1);
     }
 
-    sp<GLConsumer> surfaceTexture(new GLConsumer(consumer, texName,
-            GL_TEXTURE_EXTERNAL_OES, true, true));
+    sp<GLConsumer> surfaceTexture;
+    if (isDetached) {
+        surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES,
+                true, true);
+    } else {
+        surfaceTexture = new GLConsumer(consumer, texName,
+                GL_TEXTURE_EXTERNAL_OES, true, true);
+    }
+
     if (surfaceTexture == 0) {
         jniThrowException(env, OutOfResourcesException,
                 "Unable to create native SurfaceTexture");
@@ -338,7 +345,7 @@
 
 static JNINativeMethod gSurfaceTextureMethods[] = {
     {"nativeClassInit",            "()V",   (void*)SurfaceTexture_classInit },
-    {"nativeInit",                 "(IZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
+    {"nativeInit",                 "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
     {"nativeFinalize",             "()V",   (void*)SurfaceTexture_finalize },
     {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
     {"nativeUpdateTexImage",       "()V",   (void*)SurfaceTexture_updateTexImage },
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
deleted file mode 100644
index d0b6f7c..0000000
--- a/core/jni/android/graphics/TextLayout.cpp
+++ /dev/null
@@ -1,102 +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 "TextLayout"
-
-#include "TextLayout.h"
-#include "TextLayoutCache.h"
-
-#include <android_runtime/AndroidRuntime.h>
-
-#include "SkTemplates.h"
-#include "unicode/ubidi.h"
-#include "unicode/ushape.h"
-#include <utils/Log.h>
-
-namespace android {
-
-// Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
-// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
-// looking for a character >= the first RTL character in unicode and assume we do if
-// we find one.
-bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
-    if (bidiFlags == kBidi_Force_LTR) {
-        return false;
-    }
-    if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
-            bidiFlags == kBidi_Force_RTL) {
-        return true;
-    }
-    for (int i = 0; i < len; ++i) {
-        if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
-            return true;
-        }
-    }
-    return false;
-}
-
-// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
-// This will draw if canvas is not null, otherwise path must be non-null and it will create
-// a path representing the text that would have been drawn.
-void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
-                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, len, len, bidiFlags);
-    if (value == NULL) {
-        return ;
-    }
-    // Beware: this needs Glyph encoding (already done on the Paint constructor)
-    paint->getTextPath(value->getGlyphs(), value->getGlyphsCount() * 2, x, y, path);
-}
-
-void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                    jint count, jint contextCount, jint dirFlags,
-                                    jfloat* resultAdvances, jfloat* resultTotalAdvance) {
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            chars, start, count, contextCount, dirFlags);
-    if (value == NULL) {
-        return ;
-    }
-    if (resultAdvances) {
-        memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat));
-    }
-    if (resultTotalAdvance) {
-        *resultTotalAdvance = value->getTotalAdvance();
-    }
-}
-
-void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
-                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
-    handleText(paint, text, len, bidiFlags, x, y, path);
-}
-
-
-void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
-                                int bidiFlags, jfloat hOffset, jfloat vOffset,
-                                SkPath* path, SkCanvas* canvas) {
-
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count, bidiFlags);
-    if (value == NULL) {
-        return;
-    }
-
-    // Beware: this needs Glyph encoding (already done on the Paint constructor)
-    canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path,
-            hOffset, vOffset, *paint);
-}
-
-}
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
deleted file mode 100644
index 495d08a..0000000
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ /dev/null
@@ -1,948 +0,0 @@
-/*
- * Copyright (C) 2011 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 "TextLayoutCache"
-
-#include <utils/JenkinsHash.h>
-#include <utils/CallStack.h>
-
-#include "TextLayoutCache.h"
-#include "TextLayout.h"
-#include "SkGlyphCache.h"
-#include "SkTypeface_android.h"
-#include "HarfBuzzNGFaceSkia.h"
-#include <unicode/unistr.h>
-#include <unicode/uchar.h>
-#include <hb-icu.h>
-
-namespace android {
-
-//--------------------------------------------------------------------------------------------------
-
-ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
-
-//--------------------------------------------------------------------------------------------------
-
-TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
-        mShaper(shaper),
-        mCache(LruCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
-        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
-        mCacheHitCount(0), mNanosecondsSaved(0) {
-    init();
-}
-
-TextLayoutCache::~TextLayoutCache() {
-    mCache.clear();
-}
-
-void TextLayoutCache::init() {
-    mCache.setOnEntryRemovedListener(this);
-
-    mDebugLevel = readRtlDebugLevel();
-    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
-    ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
-
-    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-
-    if (mDebugEnabled) {
-        ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
-    }
-
-    mInitialized = true;
-}
-
-/**
- *  Callbacks
- */
-void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
-    size_t totalSizeToDelete = text.getSize() + desc->getSize();
-    mSize -= totalSizeToDelete;
-    if (mDebugEnabled) {
-        ALOGD("Cache value %p deleted, size = %zu", desc.get(), totalSizeToDelete);
-    }
-}
-
-/*
- * Cache clearing
- */
-void TextLayoutCache::purgeCaches() {
-    AutoMutex _l(mLock);
-    mCache.clear();
-    mShaper->purgeCaches();
-}
-
-/*
- * Caching
- */
-sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
-            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
-    AutoMutex _l(mLock);
-#ifdef USE_MINIKIN
-    // We want to get rid of all legacy calls in the Minikin case, so log
-    ALOGW("TextLayoutCache being invoked!");
-    CallStack _cs(LOG_TAG);
-#endif
-    nsecs_t startTime = 0;
-    if (mDebugEnabled) {
-        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    }
-
-    // Create the key
-    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
-
-    // Get value from cache if possible
-    sp<TextLayoutValue> value = mCache.get(key);
-
-    // Value not found for the key, we need to add a new value in the cache
-    if (value == NULL) {
-        if (mDebugEnabled) {
-            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
-        }
-
-        value = new TextLayoutValue(contextCount);
-
-        // Compute advances and store them
-        mShaper->computeValues(value.get(), paint,
-                reinterpret_cast<const UChar*>(key.getText()), start, count,
-                size_t(contextCount), int(dirFlags));
-
-        if (mDebugEnabled) {
-            value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
-        }
-
-        // Don't bother to add in the cache if the entry is too big
-        size_t size = key.getSize() + value->getSize();
-        if (size <= mMaxSize) {
-            // Cleanup to make some room if needed
-            if (mSize + size > mMaxSize) {
-                if (mDebugEnabled) {
-                    ALOGD("Need to clean some entries for making some room for a new entry");
-                }
-                while (mSize + size > mMaxSize) {
-                    // This will call the callback
-                    bool removedOne = mCache.removeOldest();
-                    LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
-                            "failed to remove the oldest entry.  "
-                            "mSize = %u, size = %zu, mMaxSize = %u, mCache.size() = %zu",
-                            mSize, size, mMaxSize, mCache.size());
-                }
-            }
-
-            // Update current cache size
-            mSize += size;
-
-            bool putOne = mCache.put(key, value);
-            LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
-                    "This indicates that the cache already has an entry with the "
-                    "same key but it should not since we checked earlier!"
-                    " - start = %d, count = %d, contextCount = %d - Text = '%s'",
-                    start, count, contextCount, String8(key.getText() + start, count).string());
-
-            if (mDebugEnabled) {
-                nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
-                ALOGD("CACHE MISS: Added entry %p "
-                        "with start = %d, count = %d, contextCount = %d, "
-                        "entry size %zu bytes, remaining space %d bytes"
-                        " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
-                        value.get(), start, count, contextCount, size, mMaxSize - mSize,
-                        value->getElapsedTime() * 0.000001f,
-                        (totalTime - value->getElapsedTime()) * 0.000001f,
-                        String8(key.getText() + start, count).string());
-            }
-        } else {
-            if (mDebugEnabled) {
-                ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
-                        "with start = %d, count = %d, contextCount = %d, "
-                        "entry size %zu bytes, remaining space %d bytes"
-                        " - Compute time %0.6f ms - Text = '%s'",
-                        start, count, contextCount, size, mMaxSize - mSize,
-                        value->getElapsedTime() * 0.000001f,
-                        String8(key.getText() + start, count).string());
-            }
-        }
-    } else {
-        // This is a cache hit, just log timestamp and user infos
-        if (mDebugEnabled) {
-            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
-            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
-            ++mCacheHitCount;
-
-            if (value->getElapsedTime() > 0) {
-                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
-                        / ((float)value->getElapsedTime()));
-                ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
-                        "- Compute time %0.6f ms - "
-                        "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
-                        mCacheHitCount, start, count, contextCount,
-                        value->getElapsedTime() * 0.000001f,
-                        elapsedTimeThruCacheGet * 0.000001f,
-                        deltaPercent,
-                        String8(key.getText() + start, count).string());
-            }
-            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
-                dumpCacheStats();
-            }
-        }
-    }
-    return value;
-}
-
-void TextLayoutCache::dumpCacheStats() {
-    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
-    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
-
-    size_t cacheSize = mCache.size();
-
-    ALOGD("------------------------------------------------");
-    ALOGD("Cache stats");
-    ALOGD("------------------------------------------------");
-    ALOGD("pid       : %d", getpid());
-    ALOGD("running   : %.0f seconds", timeRunningInSec);
-    ALOGD("entries   : %zu", cacheSize);
-    ALOGD("max size  : %d bytes", mMaxSize);
-    ALOGD("used      : %d bytes according to mSize", mSize);
-    ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
-    ALOGD("hits      : %d", mCacheHitCount);
-    ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
-    ALOGD("------------------------------------------------");
-}
-
-/**
- * TextLayoutCacheKey
- */
-TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
-        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
-        hinting(SkPaint::kNo_Hinting) {
-    paintOpts.setUseFontFallbacks(true);
-}
-
-TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
-        size_t start, size_t count, size_t contextCount, int dirFlags) :
-            start(start), count(count), contextCount(contextCount),
-            dirFlags(dirFlags) {
-    textCopy.setTo(text, contextCount);
-    typeface = paint->getTypeface();
-    textSize = paint->getTextSize();
-    textSkewX = paint->getTextSkewX();
-    textScaleX = paint->getTextScaleX();
-    flags = paint->getFlags();
-    hinting = paint->getHinting();
-    paintOpts = paint->getPaintOptionsAndroid();
-}
-
-TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
-        textCopy(other.textCopy),
-        start(other.start),
-        count(other.count),
-        contextCount(other.contextCount),
-        dirFlags(other.dirFlags),
-        typeface(other.typeface),
-        textSize(other.textSize),
-        textSkewX(other.textSkewX),
-        textScaleX(other.textScaleX),
-        flags(other.flags),
-        hinting(other.hinting),
-        paintOpts(other.paintOpts) {
-}
-
-int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
-    int deltaInt = lhs.start - rhs.start;
-    if (deltaInt != 0) return (deltaInt);
-
-    deltaInt = lhs.count - rhs.count;
-    if (deltaInt != 0) return (deltaInt);
-
-    deltaInt = lhs.contextCount - rhs.contextCount;
-    if (deltaInt != 0) return (deltaInt);
-
-    if (lhs.typeface < rhs.typeface) return -1;
-    if (lhs.typeface > rhs.typeface) return +1;
-
-    if (lhs.textSize < rhs.textSize) return -1;
-    if (lhs.textSize > rhs.textSize) return +1;
-
-    if (lhs.textSkewX < rhs.textSkewX) return -1;
-    if (lhs.textSkewX > rhs.textSkewX) return +1;
-
-    if (lhs.textScaleX < rhs.textScaleX) return -1;
-    if (lhs.textScaleX > rhs.textScaleX) return +1;
-
-    deltaInt = lhs.flags - rhs.flags;
-    if (deltaInt != 0) return (deltaInt);
-
-    deltaInt = lhs.hinting - rhs.hinting;
-    if (deltaInt != 0) return (deltaInt);
-
-    deltaInt = lhs.dirFlags - rhs.dirFlags;
-    if (deltaInt) return (deltaInt);
-
-    if (lhs.paintOpts != rhs.paintOpts)
-        return memcmp(&lhs.paintOpts, &rhs.paintOpts, sizeof(SkPaintOptionsAndroid));
-
-    return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
-}
-
-size_t TextLayoutCacheKey::getSize() const {
-    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
-}
-
-hash_t TextLayoutCacheKey::hash() const {
-    uint32_t hash = JenkinsHashMix(0, start);
-    hash = JenkinsHashMix(hash, count);
-    /* contextCount not needed because it's included in text, below */
-    hash = JenkinsHashMix(hash, hash_type(typeface));
-    hash = JenkinsHashMix(hash, hash_type(textSize));
-    hash = JenkinsHashMix(hash, hash_type(textSkewX));
-    hash = JenkinsHashMix(hash, hash_type(textScaleX));
-    hash = JenkinsHashMix(hash, flags);
-    hash = JenkinsHashMix(hash, hinting);
-    hash = JenkinsHashMix(hash, paintOpts.getFontVariant());
-    // Note: leaving out language is not problematic, as equality comparisons
-    // are still valid - the only bad thing that could happen is collisions.
-    hash = JenkinsHashMixShorts(hash, getText(), contextCount);
-    return JenkinsHashWhiten(hash);
-}
-
-/**
- * TextLayoutCacheValue
- */
-TextLayoutValue::TextLayoutValue(size_t contextCount) :
-        mTotalAdvance(0), mElapsedTime(0) {
-    mBounds.setEmpty();
-    // Give a hint for advances and glyphs vectors size
-    mAdvances.setCapacity(contextCount);
-    mGlyphs.setCapacity(contextCount);
-    mPos.setCapacity(contextCount * 2);
-}
-
-size_t TextLayoutValue::getSize() const {
-    return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
-            sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
-}
-
-void TextLayoutValue::setElapsedTime(uint32_t time) {
-    mElapsedTime = time;
-}
-
-uint32_t TextLayoutValue::getElapsedTime() {
-    return mElapsedTime;
-}
-
-TextLayoutShaper::TextLayoutShaper() {
-    mBuffer = hb_buffer_create();
-}
-
-TextLayoutShaper::~TextLayoutShaper() {
-    hb_buffer_destroy(mBuffer);
-}
-
-void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint,
-        const UChar* chars, size_t start, size_t count, size_t contextCount, int dirFlags) {
-    computeValues(paint, chars, start, count, contextCount, dirFlags,
-            &value->mAdvances, &value->mTotalAdvance, &value->mBounds,
-            &value->mGlyphs, &value->mPos);
-#if DEBUG_ADVANCES
-    ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
-            contextCount, value->mTotalAdvance);
-#endif
-}
-
-void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
-        size_t start, size_t count, size_t contextCount, int dirFlags,
-        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
-        Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
-        *outTotalAdvance = 0;
-        if (!count) {
-            return;
-        }
-
-        UBiDiLevel bidiReq = 0;
-        bool forceLTR = false;
-        bool forceRTL = false;
-
-        switch (dirFlags & kBidi_Mask) {
-            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
-            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
-            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
-            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
-            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
-            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
-        }
-
-        bool useSingleRun = false;
-        bool isRTL = forceRTL;
-        if (forceLTR || forceRTL) {
-            useSingleRun = true;
-        } else {
-            UBiDi* bidi = ubidi_open();
-            if (bidi) {
-                UErrorCode status = U_ZERO_ERROR;
-#if DEBUG_GLYPHS
-                ALOGD("******** ComputeValues -- start");
-                ALOGD("      -- string = '%s'", String8(chars + start, count).string());
-                ALOGD("      -- start = %d", start);
-                ALOGD("      -- count = %d", count);
-                ALOGD("      -- contextCount = %d", contextCount);
-                ALOGD("      -- bidiReq = %d", bidiReq);
-#endif
-                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
-                if (U_SUCCESS(status)) {
-                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
-                    ssize_t rc = ubidi_countRuns(bidi, &status);
-#if DEBUG_GLYPHS
-                    ALOGD("      -- dirFlags = %d", dirFlags);
-                    ALOGD("      -- paraDir = %d", paraDir);
-                    ALOGD("      -- run-count = %d", int(rc));
-#endif
-                    if (U_SUCCESS(status) && rc == 1) {
-                        // Normal case: one run, status is ok
-                        isRTL = (paraDir == 1);
-                        useSingleRun = true;
-                    } else if (!U_SUCCESS(status) || rc < 1) {
-                        ALOGW("Need to force to single run -- string = '%s',"
-                                " status = %d, rc = %d",
-                                String8(chars + start, count).string(), status, int(rc));
-                        isRTL = (paraDir == 1);
-                        useSingleRun = true;
-                    } else {
-                        int32_t end = start + count;
-                        for (size_t i = 0; i < size_t(rc); ++i) {
-                            int32_t startRun = -1;
-                            int32_t lengthRun = -1;
-                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
-
-                            if (startRun == -1 || lengthRun == -1) {
-                                // Something went wrong when getting the visual run, need to clear
-                                // already computed data before doing a single run pass
-                                ALOGW("Visual run is not valid");
-                                outGlyphs->clear();
-                                outAdvances->clear();
-                                outPos->clear();
-                                *outTotalAdvance = 0;
-                                isRTL = (paraDir == 1);
-                                useSingleRun = true;
-                                break;
-                            }
-
-                            if (startRun >= end) {
-                                continue;
-                            }
-                            int32_t endRun = startRun + lengthRun;
-                            if (endRun <= int32_t(start)) {
-                                continue;
-                            }
-                            if (startRun < int32_t(start)) {
-                                startRun = int32_t(start);
-                            }
-                            if (endRun > end) {
-                                endRun = end;
-                            }
-
-                            lengthRun = endRun - startRun;
-                            isRTL = (runDir == UBIDI_RTL);
-#if DEBUG_GLYPHS
-                            ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
-                                    i, startRun, lengthRun, isRTL);
-#endif
-                            computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
-                                    outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
-
-                        }
-                    }
-                } else {
-                    ALOGW("Cannot set Para");
-                    useSingleRun = true;
-                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
-                }
-                ubidi_close(bidi);
-            } else {
-                ALOGW("Cannot ubidi_open()");
-                useSingleRun = true;
-                isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
-            }
-        }
-
-        // Default single run case
-        if (useSingleRun){
-#if DEBUG_GLYPHS
-            ALOGD("Using a SINGLE BiDi Run "
-                    "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
-#endif
-            computeRunValues(paint, chars, start, count, contextCount, isRTL,
-                    outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
-        }
-
-#if DEBUG_GLYPHS
-        ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
-        ALOGD("******** ComputeValues -- end");
-#endif
-}
-
-#define HB_IsHighSurrogate(ucs) \
-    (((ucs) & 0xfc00) == 0xd800)
-
-#define HB_IsLowSurrogate(ucs) \
-    (((ucs) & 0xfc00) == 0xdc00)
-
-#ifndef HB_SurrogateToUcs4
-#define HB_SurrogateToUcs4(high, low) \
-    (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
-#endif
-
-#define HB_InvalidCodePoint ~0u
-
-hb_codepoint_t
-utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
-  const uint16_t v = chars[(*iter)++];
-  if (HB_IsHighSurrogate(v)) {
-    // surrogate pair
-    if (size_t(*iter) >= len) {
-      // the surrogate is incomplete.
-      return HB_InvalidCodePoint;
-    }
-    const uint16_t v2 = chars[(*iter)++];
-    if (!HB_IsLowSurrogate(v2)) {
-      // invalidate surrogate pair.
-      (*iter)--;
-      return HB_InvalidCodePoint;
-    }
-
-    return HB_SurrogateToUcs4(v, v2);
-  }
-
-  if (HB_IsLowSurrogate(v)) {
-    // this isn't a valid code point
-    return HB_InvalidCodePoint;
-  }
-
-  return v;
-}
-
-hb_codepoint_t
-utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
-  const uint16_t v = chars[(*iter)--];
-  if (HB_IsLowSurrogate(v)) {
-    // surrogate pair
-    if (*iter < 0) {
-      // the surrogate is incomplete.
-      return HB_InvalidCodePoint;
-    }
-    const uint16_t v2 = chars[(*iter)--];
-    if (!HB_IsHighSurrogate(v2)) {
-      // invalidate surrogate pair.
-      (*iter)++;
-      return HB_InvalidCodePoint;
-    }
-
-    return HB_SurrogateToUcs4(v2, v);
-  }
-
-  if (HB_IsHighSurrogate(v)) {
-    // this isn't a valid code point
-    return HB_InvalidCodePoint;
-  }
-
-  return v;
-}
-
-struct ScriptRun {
-    hb_script_t script;
-    size_t pos;
-    size_t length;
-};
-
-hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
-    static hb_unicode_funcs_t* u;
-    if (!u) {
-        u = hb_icu_get_unicode_funcs();
-    }
-    return hb_unicode_script(u, codepoint);
-}
-
-bool
-hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
-  if (size_t(*iter) == len)
-    return false;
-
-  run->pos = *iter;
-  const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
-  const hb_script_t init_script = code_point_to_script(init_cp);
-  hb_script_t current_script = init_script;
-  run->script = init_script;
-
-  for (;;) {
-    if (size_t(*iter) == len)
-      break;
-    const ssize_t prev_iter = *iter;
-    const uint32_t cp = utf16_to_code_point(chars, len, iter);
-    const hb_script_t script = code_point_to_script(cp);
-
-    if (script != current_script) {
-        /* BEGIN android-changed
-           The condition was not correct by doing "a == b == constant"
-           END android-changed */
-      if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
-        // If we started off as inherited, we take whatever we can find.
-        run->script = script;
-        current_script = script;
-        continue;
-      } else if (script == HB_SCRIPT_INHERITED) {
-        continue;
-      } else {
-        *iter = prev_iter;
-        break;
-      }
-    }
-  }
-
-  if (run->script == HB_SCRIPT_INHERITED)
-    run->script = HB_SCRIPT_COMMON;
-
-  run->length = *iter - run->pos;
-  return true;
-}
-
-bool
-hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
-  if (*iter == -1)
-    return false;
-
-  const size_t ending_index = *iter;
-  const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
-  const hb_script_t init_script = code_point_to_script(init_cp);
-  hb_script_t current_script = init_script;
-  run->script = init_script;
-  size_t break_iter = *iter;
-
-  for (;;) {
-    if (*iter < 0)
-      break;
-    const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
-    const hb_script_t script = code_point_to_script(cp);
-
-    if (script != current_script) {
-      if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
-        // If we started off as inherited, we take whatever we can find.
-        run->script = script;
-        current_script = script;
-        // In cases of script1 + inherited + script2, always group the inherited
-        // with script1.
-        break_iter = *iter;
-        continue;
-      } else if (script == HB_SCRIPT_INHERITED) {
-        continue;
-      } else {
-        *iter = break_iter;
-        break;
-      }
-    } else {
-        break_iter = *iter;
-    }
-  }
-
-  if (run->script == HB_SCRIPT_INHERITED)
-    run->script = HB_SCRIPT_COMMON;
-
-  run->pos = *iter + 1;
-  run->length = ending_index - *iter;
-  return true;
-}
-
-
-static void logGlyphs(hb_buffer_t* buffer) {
-    unsigned int numGlyphs;
-    hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
-    hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
-    ALOGD("         -- glyphs count=%d", numGlyphs);
-    for (size_t i = 0; i < numGlyphs; i++) {
-        ALOGD("         -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
-                info[i].codepoint,
-                info[i].cluster,
-                HBFixedToFloat(positions[i].x_advance),
-                HBFixedToFloat(positions[i].x_offset),
-                HBFixedToFloat(positions[i].y_offset));
-    }
-}
-
-void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
-        size_t start, size_t count, size_t contextCount, bool isRTL,
-        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
-        Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
-    if (!count) {
-        // We cannot shape an empty run.
-        return;
-    }
-
-    // To be filled in later
-    for (size_t i = 0; i < count; i++) {
-        outAdvances->add(0);
-    }
-
-    // Set the string properties
-    const UChar* chars = contextChars + start;
-
-    // Define shaping paint properties
-    mShapingPaint.setTextSize(paint->getTextSize());
-    float skewX = paint->getTextSkewX();
-    mShapingPaint.setTextSkewX(skewX);
-    mShapingPaint.setTextScaleX(paint->getTextScaleX());
-    mShapingPaint.setFlags(paint->getFlags());
-    mShapingPaint.setHinting(paint->getHinting());
-    mShapingPaint.setPaintOptionsAndroid(paint->getPaintOptionsAndroid());
-
-    // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
-    // into the shaperItem
-    ssize_t indexFontRun = isRTL ? count - 1 : 0;
-    jfloat totalAdvance = *outTotalAdvance;
-    ScriptRun run;  // relative to chars
-    while ((isRTL) ?
-            hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
-            hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
-
-#if DEBUG_GLYPHS
-        ALOGD("-------- Start of Script Run --------");
-        ALOGD("Shaping Script Run with");
-        ALOGD("         -- isRTL = %d", isRTL);
-        ALOGD("         -- HB script = %c%c%c%c", HB_UNTAG(run.script));
-        ALOGD("         -- run.pos = %d", int(run.pos));
-        ALOGD("         -- run.length = %d", int(run.length));
-        ALOGD("         -- run = '%s'", String8(chars + run.pos, run.length).string());
-        ALOGD("         -- string = '%s'", String8(chars, count).string());
-#endif
-
-        hb_buffer_reset(mBuffer);
-        // Note: if we want to set unicode functions, etc., this is the place.
-        
-        hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
-        hb_buffer_set_script(mBuffer, run.script);
-        SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
-        hb_buffer_set_language(mBuffer, hb_language_from_string(langString.c_str(), -1));
-        hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
-
-        // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
-        // and shape the Font run
-        size_t glyphBaseCount = shapeFontRun(paint);
-        unsigned int numGlyphs;
-        hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
-        hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
-
-#if DEBUG_GLYPHS
-        ALOGD("Got from Harfbuzz");
-        ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
-        ALOGD("         -- num_glyph = %d", numGlyphs);
-        ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
-        ALOGD("         -- initial totalAdvance = %f", totalAdvance);
-
-        logGlyphs(mBuffer);
-#endif
-
-        for (size_t i = 0; i < numGlyphs; i++) {
-            size_t cluster = info[i].cluster - start;
-            float xAdvance = HBFixedToFloat(positions[i].x_advance);
-            outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
-            jchar glyphId = info[i].codepoint + glyphBaseCount;
-            outGlyphs->add(glyphId);
-            float xo = HBFixedToFloat(positions[i].x_offset);
-            float yo = -HBFixedToFloat(positions[i].y_offset);
-
-            float xpos = totalAdvance + xo + yo * skewX;
-            float ypos = yo;
-            outPos->add(xpos);
-            outPos->add(ypos);
-            totalAdvance += xAdvance;
-
-            SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL);
-            const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId);
-            outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop,
-                    xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight);
-
-        }
-    }
-
-    *outTotalAdvance = totalAdvance;
-
-#if DEBUG_GLYPHS
-    ALOGD("         -- final totalAdvance = %f", totalAdvance);
-    ALOGD("-------- End of Script Run --------");
-#endif
-}
-
-/**
- * Return the first typeface in the logical change, starting with this typeface,
- * that contains the specified unichar, or NULL if none is found.
- */
-SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
-        hb_script_t script) {
-    SkTypeface::Style currentStyle = SkTypeface::kNormal;
-    if (typeface) {
-        currentStyle = typeface->style();
-    }
-    typeface = SkCreateTypefaceForScript(script, currentStyle);
-#if DEBUG_GLYPHS
-    ALOGD("Using Harfbuzz Script %c%c%c%c, Style %d", HB_UNTAG(script), currentStyle);
-#endif
-    return typeface;
-}
-
-bool TextLayoutShaper::isComplexScript(hb_script_t script) {
-    switch (script) {
-    case HB_SCRIPT_COMMON:
-    case HB_SCRIPT_GREEK:
-    case HB_SCRIPT_CYRILLIC:
-    case HB_SCRIPT_HANGUL:
-    case HB_SCRIPT_INHERITED:
-    case HB_SCRIPT_HAN:
-    case HB_SCRIPT_KATAKANA:
-    case HB_SCRIPT_HIRAGANA:
-        return false;
-    default:
-        return true;
-    }
-}
-
-size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
-    // Update Harfbuzz Shaper
-
-    SkTypeface* typeface = paint->getTypeface();
-
-    // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
-    // This is needed as the Typeface used for shaping can be not the default one
-    // when we are shaping any script that needs to use a fallback Font.
-    // If we are a "common" script we dont need to shift
-    size_t baseGlyphCount = 0;
-    hb_codepoint_t firstUnichar = 0;
-    if (isComplexScript(hb_buffer_get_script(mBuffer))) {
-        unsigned int numGlyphs;
-        hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
-        for (size_t i = 0; i < numGlyphs; i++) {
-            firstUnichar = info[i].codepoint;
-            if (firstUnichar != ' ') {
-                break;
-            }
-        }
-        baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
-    }
-
-    SkTypeface* scriptTypeface = NULL;
-    if (baseGlyphCount != 0) {
-        scriptTypeface = typefaceForScript(paint, typeface,
-            hb_buffer_get_script(mBuffer));
-#if DEBUG_GLYPHS
-        ALOGD("Using Default Typeface for script %c%c%c%c",
-            HB_UNTAG(hb_buffer_get_script(mBuffer)));
-#endif
-    }
-    if (scriptTypeface) {
-        typeface = scriptTypeface;
-    } else {
-        baseGlyphCount = 0;
-        if (typeface) {
-            SkSafeRef(typeface);
-        } else {
-            typeface = SkTypeface::CreateFromName(NULL, SkTypeface::kNormal);
-#if DEBUG_GLYPHS
-            ALOGD("Using Default Typeface (normal style)");
-#endif
-        }
-    }
-
-    mShapingPaint.setTypeface(typeface);
-    hb_face_t* face = referenceCachedHBFace(typeface);
-
-    float sizeY = paint->getTextSize();
-    float sizeX = sizeY * paint->getTextScaleX();
-    hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
-    hb_face_destroy(face);
-
-#if DEBUG_GLYPHS
-    ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
-            typeface, typeface->uniqueID(), face);
-#endif
-    SkSafeUnref(typeface);
-
-    hb_shape(font, mBuffer, NULL, 0);
-    hb_font_destroy(font);
-
-    mShapingPaint.setTypeface(paint->getTypeface());
-    return baseGlyphCount;
-}
-
-hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
-    SkFontID fontId = typeface->uniqueID();
-    ssize_t index = mCachedHBFaces.indexOfKey(fontId);
-    if (index >= 0) {
-        return hb_face_reference(mCachedHBFaces.valueAt(index));
-    }
-    // TODO: destroy function
-    hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
-#if DEBUG_GLYPHS
-    ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
-#endif
-    mCachedHBFaces.add(fontId, face);
-    return hb_face_reference(face);
-}
-
-void TextLayoutShaper::purgeCaches() {
-    size_t cacheSize = mCachedHBFaces.size();
-    for (size_t i = 0; i < cacheSize; i++) {
-        hb_face_destroy(mCachedHBFaces.valueAt(i));
-    }
-    mCachedHBFaces.clear();
-}
-
-TextLayoutEngine::TextLayoutEngine() {
-    mShaper = new TextLayoutShaper();
-#if USE_TEXT_LAYOUT_CACHE
-    mTextLayoutCache = new TextLayoutCache(mShaper);
-#else
-    mTextLayoutCache = NULL;
-#endif
-}
-
-TextLayoutEngine::~TextLayoutEngine() {
-    delete mTextLayoutCache;
-    delete mShaper;
-}
-
-sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
-        jint start, jint count, jint contextCount, jint dirFlags) {
-    sp<TextLayoutValue> value;
-#if USE_TEXT_LAYOUT_CACHE
-    value = mTextLayoutCache->getValue(paint, text, start, count,
-            contextCount, dirFlags);
-    if (value == NULL) {
-        ALOGE("Cannot get TextLayoutCache value for text = '%s'",
-                String8(text + start, count).string());
-    }
-#else
-    value = new TextLayoutValue(count);
-    mShaper->computeValues(value.get(), paint,
-            reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
-#endif
-    return value;
-}
-
-void TextLayoutEngine::purgeCaches() {
-#if USE_TEXT_LAYOUT_CACHE
-    mTextLayoutCache->purgeCaches();
-#if DEBUG_GLYPHS
-    ALOGD("Purged TextLayoutEngine caches");
-#endif
-#endif
-}
-
-
-} // namespace android
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
deleted file mode 100644
index 54704ec..0000000
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2011 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_TEXT_LAYOUT_CACHE_H
-#define ANDROID_TEXT_LAYOUT_CACHE_H
-
-#include "RtlProperties.h"
-
-#include <stddef.h>
-#include <utils/threads.h>
-#include <utils/String16.h>
-#include <utils/LruCache.h>
-#include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
-#include <utils/Singleton.h>
-
-#include <SkAutoKern.h>
-#include <SkPaint.h>
-#include <SkTemplates.h>
-#include <SkTypeface.h>
-#include <SkUtils.h>
-
-#include <unicode/ubidi.h>
-#include <unicode/unistr.h>
-
-#include <hb.h>
-
-#include <android_runtime/AndroidRuntime.h>
-
-#define UNICODE_NOT_A_CHAR              0xffff
-#define UNICODE_ZWSP                    0x200b
-#define UNICODE_FIRST_LOW_SURROGATE     0xdc00
-#define UNICODE_FIRST_HIGH_SURROGATE    0xd800
-#define UNICODE_FIRST_PRIVATE_USE       0xe000
-#define UNICODE_FIRST_RTL_CHAR          0x0590
-
-// Temporary buffer size
-#define CHAR_BUFFER_SIZE 80
-
-// Converts a number of mega-bytes into bytes
-#define MB(s) s * 1024 * 1024
-
-// Define the default cache size in Mb
-#define DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB 0.500f
-
-// Define the interval in number of cache hits between two statistics dump
-#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
-
-namespace android {
-
-/**
- * TextLayoutCacheKey is the Cache key
- */
-class TextLayoutCacheKey {
-public:
-    TextLayoutCacheKey();
-
-    TextLayoutCacheKey(const SkPaint* paint, const UChar* text, size_t start, size_t count,
-            size_t contextCount, int dirFlags);
-
-    TextLayoutCacheKey(const TextLayoutCacheKey& other);
-
-    /**
-     * Get the size of the Cache key.
-     */
-    size_t getSize() const;
-
-    static int compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs);
-
-    inline const UChar* getText() const { return textCopy.string(); }
-
-    bool operator==(const TextLayoutCacheKey& other) const {
-        return compare(*this, other) == 0;
-    }
-
-    bool operator!=(const TextLayoutCacheKey& other) const {
-        return compare(*this, other) != 0;
-    }
-
-    hash_t hash() const;
-private:
-    String16 textCopy;
-    size_t start;
-    size_t count;
-    size_t contextCount;
-    int dirFlags;
-    SkTypeface* typeface;
-    SkScalar textSize;
-    SkScalar textSkewX;
-    SkScalar textScaleX;
-    uint32_t flags;
-    SkPaint::Hinting hinting;
-    SkPaintOptionsAndroid paintOpts;
-
-}; // TextLayoutCacheKey
-
-inline int strictly_order_type(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
-    return TextLayoutCacheKey::compare(lhs, rhs) < 0;
-}
-
-inline int compare_type(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
-    return TextLayoutCacheKey::compare(lhs, rhs);
-}
-
-inline hash_t hash_type(const TextLayoutCacheKey& key) {
-    return key.hash();
-}
-
-/*
- * TextLayoutValue is the Cache value
- */
-class TextLayoutValue : public RefBase {
-public:
-    TextLayoutValue(size_t contextCount);
-
-    void setElapsedTime(uint32_t time);
-    uint32_t getElapsedTime();
-
-    inline const jfloat* getAdvances() const { return mAdvances.array(); }
-    inline size_t getAdvancesCount() const { return mAdvances.size(); }
-    inline jfloat getTotalAdvance() const { return mTotalAdvance; }
-    inline const SkRect& getBounds() const { return mBounds; }
-    inline const jchar* getGlyphs() const { return mGlyphs.array(); }
-    inline size_t getGlyphsCount() const { return mGlyphs.size(); }
-    inline const jfloat* getPos() const { return mPos.array(); }
-    inline size_t getPosCount() const { return mPos.size(); }
-
-    /**
-     * Advances vector
-     */
-    Vector<jfloat> mAdvances;
-
-    /**
-     * Total number of advances
-     */
-    jfloat mTotalAdvance;
-
-    /**
-     * Bounds containing all glyphs
-     */
-    SkRect mBounds;
-
-    /**
-     * Glyphs vector
-     */
-    Vector<jchar> mGlyphs;
-
-    /**
-     * Pos vector (2 * i is x pos, 2 * i + 1 is y pos, same as drawPosText)
-     */
-    Vector<jfloat> mPos;
-
-    /**
-     * Get the size of the Cache entry
-     */
-    size_t getSize() const;
-
-private:
-    /**
-     * Time for computing the values (in milliseconds)
-     */
-    uint32_t mElapsedTime;
-
-}; // TextLayoutCacheValue
-
-/**
- * The TextLayoutShaper is responsible for shaping (with the Harfbuzz library)
- */
-class TextLayoutShaper {
-public:
-    TextLayoutShaper();
-    virtual ~TextLayoutShaper();
-
-    void computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount, int dirFlags);
-
-    void purgeCaches();
-
-private:
-    /**
-     * Harfbuzz buffer for shaping
-     */
-    hb_buffer_t* mBuffer;
-
-    /**
-     * Skia Paint used for shaping
-     */
-    SkPaint mShapingPaint;
-
-    /**
-     * Cache of Harfbuzz faces
-     */
-    KeyedVector<SkFontID, hb_face_t*> mCachedHBFaces;
-
-    SkTypeface* typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
-        hb_script_t script);
-
-    size_t shapeFontRun(const SkPaint* paint);
-
-    void computeValues(const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount, int dirFlags,
-            Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
-            Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
-
-    void computeRunValues(const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount, bool isRTL,
-            Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
-            Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
-
-    SkTypeface* setCachedTypeface(SkTypeface** typeface, hb_script_t script, SkTypeface::Style style);
-    hb_face_t* referenceCachedHBFace(SkTypeface* typeface);
-
-    bool isComplexScript(hb_script_t script);
-}; // TextLayoutShaper
-
-/**
- * Cache of text layout information.
- */
-class TextLayoutCache : private OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutValue> >
-{
-public:
-    TextLayoutCache(TextLayoutShaper* shaper);
-
-    ~TextLayoutCache();
-
-    bool isInitialized() {
-        return mInitialized;
-    }
-
-    /**
-     * Used as a callback when an entry is removed from the cache
-     * Do not invoke directly
-     */
-    void operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc);
-
-    sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount, jint dirFlags);
-
-    /**
-     * Clear the cache
-     */
-    void purgeCaches();
-
-private:
-    TextLayoutShaper* mShaper;
-    Mutex mLock;
-    bool mInitialized;
-
-    LruCache<TextLayoutCacheKey, sp<TextLayoutValue> > mCache;
-
-    uint32_t mSize;
-    uint32_t mMaxSize;
-
-    uint32_t mCacheHitCount;
-    uint64_t mNanosecondsSaved;
-
-    uint64_t mCacheStartTime;
-
-    RtlDebugLevel mDebugLevel;
-    bool mDebugEnabled;
-
-    /*
-     * Class initialization
-     */
-    void init();
-
-    /**
-     * Dump Cache statistics
-     */
-    void dumpCacheStats();
-
-}; // TextLayoutCache
-
-/**
- * The TextLayoutEngine is reponsible for computing TextLayoutValues
- */
-class TextLayoutEngine : public Singleton<TextLayoutEngine> {
-public:
-    TextLayoutEngine();
-    virtual ~TextLayoutEngine();
-
-    /**
-     * Note: this method currently does a defensive copy of the text argument, in case
-     * there is concurrent mutation of it. The contract may change, and may in the
-     * future require the caller to guarantee that the contents will not change during
-     * the call. Be careful of this when doing optimization.
-     **/
-    sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount, jint dirFlags);
-
-    void purgeCaches();
-
-private:
-    TextLayoutCache* mTextLayoutCache;
-    TextLayoutShaper* mShaper;
-}; // TextLayoutEngine
-
-} // namespace android
-#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */
-
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index b20c246..cf4e838 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -27,44 +27,10 @@
 
 using namespace android;
 
-class AutoJavaStringToUTF8 {
-public:
-    AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str)
-    {
-        fCStr = env->GetStringUTFChars(str, NULL);
-    }
-    ~AutoJavaStringToUTF8()
-    {
-        fEnv->ReleaseStringUTFChars(fJStr, fCStr);
-    }
-    const char* c_str() const { return fCStr; }
-
-private:
-    JNIEnv*     fEnv;
-    jstring     fJStr;
-    const char* fCStr;
-};
-
-static jlong Typeface_create(JNIEnv* env, jobject, jstring name,
-                             jint styleHandle) {
-    SkTypeface::Style style = static_cast<SkTypeface::Style>(styleHandle);
-    TypefaceImpl* face = NULL;
-
-    if (NULL != name) {
-        AutoJavaStringToUTF8    str(env, name);
-        face = TypefaceImpl_createFromName(str.c_str(), style);
-    }
-
-    // return the default font at the best style if no exact match exists
-    if (NULL == face) {
-        face = TypefaceImpl_createFromName(NULL, style);
-    }
-    return reinterpret_cast<jlong>(face);
-}
-
 static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
     TypefaceImpl* family = reinterpret_cast<TypefaceImpl*>(familyHandle);
     TypefaceImpl* face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)style);
+    // TODO: the following logic shouldn't be necessary, the above should always succeed.
     // Try to find the closest matching font, using the standard heuristic
     if (NULL == face) {
         face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
@@ -72,9 +38,6 @@
     for (int i = 0; NULL == face && i < 4; i++) {
         face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)i);
     }
-    if (NULL == face) {
-        face = TypefaceImpl_createFromName(NULL, (SkTypeface::Style)style);
-    }
     return reinterpret_cast<jlong>(face);
 }
 
@@ -88,33 +51,6 @@
     return TypefaceImpl_getStyle(face);
 }
 
-static jlong Typeface_createFromAsset(JNIEnv* env, jobject,
-                                      jobject jassetMgr,
-                                      jstring jpath) {
-    NPE_CHECK_RETURN_ZERO(env, jassetMgr);
-    NPE_CHECK_RETURN_ZERO(env, jpath);
-
-    AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
-    if (NULL == mgr) {
-        return NULL;
-    }
-
-    AutoJavaStringToUTF8 str(env, jpath);
-    Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
-    if (NULL == asset) {
-        return NULL;
-    }
-
-    return reinterpret_cast<jlong>(TypefaceImpl_createFromAsset(asset));
-}
-
-static jlong Typeface_createFromFile(JNIEnv* env, jobject, jstring jpath) {
-    NPE_CHECK_RETURN_ZERO(env, jpath);
-
-    AutoJavaStringToUTF8 str(env, jpath);
-    return reinterpret_cast<jlong>(TypefaceImpl_createFromFile(str.c_str()));
-}
-
 static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray) {
     ScopedLongArrayRO families(env, familyArray);
     return reinterpret_cast<jlong>(TypefaceImpl_createFromFamilies(families.get(), families.size()));
@@ -128,14 +64,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 static JNINativeMethod gTypefaceMethods[] = {
-    { "nativeCreate",        "(Ljava/lang/String;I)J", (void*)Typeface_create },
     { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
     { "nativeUnref",              "(J)V",  (void*)Typeface_unref },
     { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
-    { "nativeCreateFromAsset",    "(Landroid/content/res/AssetManager;Ljava/lang/String;)J",
-                                           (void*)Typeface_createFromAsset },
-    { "nativeCreateFromFile",     "(Ljava/lang/String;)J",
-                                           (void*)Typeface_createFromFile },
     { "nativeCreateFromArray",    "([J)J",
                                            (void*)Typeface_createFromArray },
     { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp
index 1800d0c..7767b8d 100644
--- a/core/jni/android/graphics/TypefaceImpl.cpp
+++ b/core/jni/android/graphics/TypefaceImpl.cpp
@@ -27,22 +27,18 @@
 #include "SkStream.h"
 #include "SkTypeface.h"
 
-#ifdef USE_MINIKIN
 #include <vector>
 #include <minikin/FontCollection.h>
 #include <minikin/FontFamily.h>
 #include <minikin/Layout.h>
 #include "SkPaint.h"
 #include "MinikinSkia.h"
-#endif
 
 #include "TypefaceImpl.h"
 #include "Utils.h"
 
 namespace android {
 
-#ifdef USE_MINIKIN
-
 // Any weight greater than or equal to this is considered "bold" for
 // legacy API.
 static const int kBoldThreshold = 6;
@@ -135,30 +131,6 @@
     return result;
 }
 
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style) {
-    SkTypeface* face = SkTypeface::CreateFromName(name, style);
-    return createFromSkTypeface(face);
-}
-
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename) {
-    SkTypeface* face = SkTypeface::CreateFromFile(filename);
-    return createFromSkTypeface(face);
-}
-
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) {
-    SkStream* stream = new AssetStreamAdaptor(asset,
-                                              AssetStreamAdaptor::kYes_OwnAsset,
-                                              AssetStreamAdaptor::kYes_HasMemoryBase);
-    SkTypeface* face = SkTypeface::CreateFromStream(stream);
-    // SkTypeFace::CreateFromStream calls ref() on the stream, so we
-    // need to unref it here or it won't be freed later on
-    stream->unref();
-    return createFromSkTypeface(face);
-}
-
 TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) {
     std::vector<FontFamily *>familyVec;
     for (size_t i = 0; i < size; i++) {
@@ -202,52 +174,4 @@
     gDefaultTypeface = face;
 }
 
-#else  // USE_MINIKIN
-
-/* Just use SkTypeface instead. */
-
-typedef SkTypeface TypefaceImpl;
-
-TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style) {
-    return SkTypeface::CreateFromTypeface(src, style);
-}
-
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style) {
-    return SkTypeface::CreateFromName(name, style);
-}
-
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename) {
-    return SkTypeface::CreateFromFile(filename);
-}
-
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) {
-    SkStream* stream = new AssetStreamAdaptor(asset,
-                                              AssetStreamAdaptor::kYes_OwnAsset,
-                                              AssetStreamAdaptor::kYes_HasMemoryBase);
-    SkTypeface* face = SkTypeface::CreateFromStream(stream);
-    // SkTypeFace::CreateFromStream calls ref() on the stream, so we
-    // need to unref it here or it won't be freed later on
-    stream->unref();
-
-    return face;
-}
-
-TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) {
-    // Should never be called in non-Minikin builds
-    return 0;
-}
-
-void TypefaceImpl_unref(TypefaceImpl* face) {
-    SkSafeUnref(face);
-}
-
-int TypefaceImpl_getStyle(TypefaceImpl* face) {
-    return face->style();
-}
-
-void TypefaceImpl_setDefault(TypefaceImpl* face) {
-}
-
-#endif  // USE_MINIKIN
-
 }
diff --git a/core/jni/android/graphics/TypefaceImpl.h b/core/jni/android/graphics/TypefaceImpl.h
index 4e92bce..12b3403 100644
--- a/core/jni/android/graphics/TypefaceImpl.h
+++ b/core/jni/android/graphics/TypefaceImpl.h
@@ -22,13 +22,10 @@
 #include "SkTypeface.h"
 #include <androidfw/AssetManager.h>
 
-#ifdef USE_MINIKIN
 #include <minikin/FontCollection.h>
-#endif
 
 namespace android {
 
-#ifdef USE_MINIKIN
 struct TypefaceImpl {
     FontCollection *fFontCollection;
     FontStyle fStyle;
@@ -41,18 +38,9 @@
 // TODO: when #ifdef USE_MINIKIN is removed, move to member functions.
 
 TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src);
-#else
-typedef SkTypeface TypefaceImpl;
-#endif
 
 TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style);
 
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style);
-
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename);
-
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset);
-
 // When we remove the USE_MINIKIN ifdef, probably a good idea to move the casting
 // (from jlong to FontFamily*) to the caller in Typeface.cpp.
 TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size);
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_graphics_Picture.cpp b/core/jni/android_graphics_Picture.cpp
new file mode 100644
index 0000000..f827907
--- /dev/null
+++ b/core/jni/android_graphics_Picture.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2008 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 "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "Picture.h"
+
+#include "SkCanvas.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+namespace android {
+
+static jlong android_graphics_Picture_newPicture(JNIEnv* env, jobject, jlong srcHandle) {
+    const Picture* src = reinterpret_cast<Picture*>(srcHandle);
+    return reinterpret_cast<jlong>(new Picture(src));
+}
+
+static jlong android_graphics_Picture_deserialize(JNIEnv* env, jobject, jobject jstream,
+                                                  jbyteArray jstorage) {
+    Picture* picture = NULL;
+    SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
+    if (strm) {
+        picture = Picture::CreateFromStream(strm);
+        delete strm;
+    }
+    return reinterpret_cast<jlong>(picture);
+}
+
+static void android_graphics_Picture_killPicture(JNIEnv* env, jobject, jlong pictureHandle) {
+    Picture* picture = reinterpret_cast<Picture*>(pictureHandle);
+    SkASSERT(picture);
+    delete picture;
+}
+
+static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle,
+                                          jlong pictureHandle) {
+    SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+    Picture* picture = reinterpret_cast<Picture*>(pictureHandle);
+    SkASSERT(canvas);
+    SkASSERT(picture);
+    picture->draw(canvas);
+}
+
+static jboolean android_graphics_Picture_serialize(JNIEnv* env, jobject, jlong pictureHandle,
+                                                   jobject jstream, jbyteArray jstorage) {
+    Picture* picture = reinterpret_cast<Picture*>(pictureHandle);
+    SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+
+    if (NULL != strm) {
+        picture->serialize(strm);
+        delete strm;
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
+static jint android_graphics_Picture_getWidth(JNIEnv* env, jobject, jlong pictureHandle) {
+    Picture* pict = reinterpret_cast<Picture*>(pictureHandle);
+    return static_cast<jint>(pict->width());
+}
+
+static jint android_graphics_Picture_getHeight(JNIEnv* env, jobject, jlong pictureHandle) {
+    Picture* pict = reinterpret_cast<Picture*>(pictureHandle);
+    return static_cast<jint>(pict->height());
+}
+
+static jlong android_graphics_Picture_beginRecording(JNIEnv* env, jobject, jlong pictHandle,
+                                                     jint w, jint h) {
+    Picture* pict = reinterpret_cast<Picture*>(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
+    // unref in its finalizer, so we have to ref it here, so that both that
+    // Canvas.java and our picture can both be owners
+    canvas->ref();
+    return reinterpret_cast<jlong>(canvas);
+}
+
+static void android_graphics_Picture_endRecording(JNIEnv* env, jobject, jlong pictHandle) {
+    Picture* pict = reinterpret_cast<Picture*>(pictHandle);
+    pict->endRecording();
+}
+
+static JNINativeMethod gMethods[] = {
+    {"nativeGetWidth", "(J)I", (void*) android_graphics_Picture_getWidth},
+    {"nativeGetHeight", "(J)I", (void*) android_graphics_Picture_getHeight},
+    {"nativeConstructor", "(J)J", (void*) android_graphics_Picture_newPicture},
+    {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)android_graphics_Picture_deserialize},
+    {"nativeBeginRecording", "(JII)J", (void*) android_graphics_Picture_beginRecording},
+    {"nativeEndRecording", "(J)V", (void*) android_graphics_Picture_endRecording},
+    {"nativeDraw", "(JJ)V", (void*) android_graphics_Picture_draw},
+    {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)android_graphics_Picture_serialize},
+    {"nativeDestructor","(J)V", (void*) android_graphics_Picture_killPicture}
+};
+
+int register_android_graphics_Picture(JNIEnv* env) {
+    return AndroidRuntime::registerNativeMethods(env, "android/graphics/Picture", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 3a53331..f8bab24 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -36,6 +36,11 @@
 
 using namespace android;
 
+enum {
+    // Keep up to date with Camera.java
+    CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2,
+};
+
 struct fields_t {
     jfieldID    context;
     jfieldID    facing;
@@ -466,7 +471,7 @@
 
 // connect to camera service
 static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
-    jobject weak_this, jint cameraId, jstring clientPackageName)
+    jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
 {
     // Convert jstring to String16
     const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
@@ -474,8 +479,18 @@
     String16 clientName(rawClientName, rawClientNameLen);
     env->ReleaseStringChars(clientPackageName, rawClientName);
 
-    sp<Camera> camera = Camera::connect(cameraId, clientName,
-            Camera::USE_CALLING_UID);
+    sp<Camera> camera;
+    if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) {
+        // Default path: hal version is don't care, do normal camera connect.
+        camera = Camera::connect(cameraId, clientName,
+                Camera::USE_CALLING_UID);
+    } else {
+        jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
+                Camera::USE_CALLING_UID, camera);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
 
     if (camera == NULL) {
         return -EACCES;
@@ -510,7 +525,6 @@
 // finalizer is invoked later.
 static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
 {
-    // TODO: Change to ALOGV
     ALOGV("release camera");
     JNICameraContext* context = NULL;
     sp<Camera> camera;
@@ -891,7 +905,7 @@
     "(ILandroid/hardware/Camera$CameraInfo;)V",
     (void*)android_hardware_Camera_getCameraInfo },
   { "native_setup",
-    "(Ljava/lang/Object;ILjava/lang/String;)I",
+    "(Ljava/lang/Object;IILjava/lang/String;)I",
     (void*)android_hardware_Camera_native_setup },
   { "native_release",
     "()V",
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index fcf8f83e..c588942 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    flags;
 } 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.flags = _env->GetFieldID(sensorClass, "mFlags",  "I");
 }
 
 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->SetIntField(sensor, sensorOffsets.flags, list->getFlags());
     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_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 40e9544..2f24a69 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "Legacy-CameraDevice-JNI"
+// #define LOG_NDEBUG 0
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/Trace.h>
@@ -26,15 +27,19 @@
 
 #include <ui/GraphicBuffer.h>
 #include <system/window.h>
+#include <hardware/camera3.h>
 
 using namespace android;
 
 // fully-qualified class name
 #define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/legacy/LegacyCameraDevice"
 #define CAMERA_DEVICE_BUFFER_SLACK  3
+#define DONT_CARE 0
 
 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
 
+#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
+
 /**
  * Convert from RGB 888 to Y'CbCr using the conversion specified in ITU-R BT.601 for
  * digital RGB with K_b = 0.114, and K_r = 0.299.
@@ -116,8 +121,9 @@
         return err;
     }
 
-    ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
-          maxBufferSlack + 1 + minUndequeuedBuffers);
+    ALOGV("%s: Setting buffer count to %d, size to (%dx%d), fmt (0x%x)", __FUNCTION__,
+          maxBufferSlack + 1 + minUndequeuedBuffers,
+          width, height, pixelFmt);
     err = native_window_set_buffer_count(anw.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
     if (err != NO_ERROR) {
         ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
@@ -146,16 +152,41 @@
                              int32_t width, // Width of the pixelBuffer
                              int32_t height, // Height of the pixelBuffer
                              int32_t pixelFmt, // Format of the pixelBuffer
-                             int64_t bufSize) {
+                             int32_t bufSize) {
     ATRACE_CALL();
     status_t err = NO_ERROR;
     ANativeWindowBuffer* anb;
-    ALOGV("%s: Dequeue buffer from %p",__FUNCTION__, anw.get());
+    ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)",
+            __FUNCTION__, anw.get(), width, height, pixelFmt, bufSize);
+
+    if (anw == 0) {
+        ALOGE("%s: anw must not be NULL", __FUNCTION__);
+        return BAD_VALUE;
+    } else if (pixelBuffer == NULL) {
+        ALOGE("%s: pixelBuffer must not be NULL", __FUNCTION__);
+        return BAD_VALUE;
+    } else if (width < 0) {
+        ALOGE("%s: width must be non-negative", __FUNCTION__);
+        return BAD_VALUE;
+    } else if (height < 0) {
+        ALOGE("%s: height must be non-negative", __FUNCTION__);
+        return BAD_VALUE;
+    } else if (bufSize < 0) {
+        ALOGE("%s: bufSize must be non-negative", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (width < 0 || height < 0 || bufSize < 0) {
+        ALOGE("%s: Illegal argument, negative dimension passed to produceFrame", __FUNCTION__);
+        return BAD_VALUE;
+    }
 
     // TODO: Switch to using Surface::lock and Surface::unlockAndPost
     err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
     if (err != NO_ERROR) return err;
 
+    // TODO: check anb is large enough to store the results
+
     sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false));
 
     switch(pixelFmt) {
@@ -181,6 +212,41 @@
                     uPlane, vPlane, chromaStep, yStride, chromaStride);
             break;
         }
+        case HAL_PIXEL_FORMAT_YV12: {
+            if (bufSize < width * height * 4) {
+                ALOGE("%s: PixelBuffer size %lld to small for given dimensions", __FUNCTION__,
+                        bufSize);
+                return BAD_VALUE;
+            }
+
+            if ((width & 1) || (height & 1)) {
+                ALOGE("%s: Dimens %dx%d are not divisible by 2.", __FUNCTION__, width, height);
+                return BAD_VALUE;
+            }
+
+            uint8_t* img = NULL;
+            ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
+            err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+            if (err != NO_ERROR) {
+                ALOGE("%s: Error %s (%d) while locking gralloc buffer for write.", __FUNCTION__,
+                        strerror(-err), err);
+                return err;
+            }
+
+            uint32_t stride = buf->getStride();
+            LOG_ALWAYS_FATAL_IF(stride % 16, "Stride is not 16 pixel aligned %d", stride);
+
+            uint32_t cStride = ALIGN(stride / 2, 16);
+            size_t chromaStep = 1;
+
+            uint8_t* yPlane = img;
+            uint8_t* crPlane = img + static_cast<uint32_t>(height) * stride;
+            uint8_t* cbPlane = crPlane + cStride * static_cast<uint32_t>(height) / 2;
+
+            rgbToYuv420(pixelBuffer, width, height, yPlane,
+                    crPlane, cbPlane, chromaStep, stride, cStride);
+            break;
+        }
         case HAL_PIXEL_FORMAT_YCbCr_420_888: {
             // Software writes with YCbCr_420_888 format are unsupported
             // by the gralloc module for now
@@ -215,7 +281,12 @@
                         err);
                 return err;
             }
+            struct camera3_jpeg_blob footer = {
+                jpeg_blob_id: CAMERA3_JPEG_BLOB_ID,
+                jpeg_size: (uint32_t)width
+            };
             memcpy(img, pixelBuffer, width);
+            memcpy(img + anb->width - sizeof(footer), &footer, sizeof(footer));
             break;
         }
         default: {
@@ -266,71 +337,72 @@
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return 0;
+        return BAD_VALUE;
     }
     int32_t fmt = 0;
     status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
     if(err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                                "Error while querying surface pixel format (error code %d)", err);
-        return 0;
+        ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__, strerror(-err),
+                err);
+        return err;
     }
     return fmt;
 }
 
-static void LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
+static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
           jobject surface, jintArray dimens) {
     ALOGV("nativeGetSurfaceDimens");
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return;
+        return BAD_VALUE;
     }
     int32_t dimenBuf[2];
     status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
     if(err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                        "Error while querying surface width (error code %d)", err);
-        return;
+        ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err),
+                err);
+        return err;
     }
     err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
     if(err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Error while querying surface height (error code %d)", err);
-        return;
+        ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
+                err);
+        return err;
     }
     env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
+    return NO_ERROR;
 }
 
-static void LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
         jint width, jint height, jint pixelFormat) {
     ALOGV("nativeConfigureSurface");
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return;
+        return BAD_VALUE;
     }
     status_t err = configureSurface(anw, width, height, pixelFormat, CAMERA_DEVICE_BUFFER_SLACK);
     if (err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Error while producing frame (error code %d)", err);
-        return;
+        ALOGE("%s: Error while configuring surface %s (%d).", __FUNCTION__, strerror(-err), err);
+        return err;
     }
+    return NO_ERROR;
 }
 
-static void LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
         jbyteArray pixelBuffer, jint width, jint height, jint pixelFormat) {
     ALOGV("nativeProduceFrame");
     sp<ANativeWindow> anw;
 
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return;
+        return BAD_VALUE;
     }
 
     if (pixelBuffer == NULL) {
         jniThrowNullPointerException(env, "pixelBuffer");
-        return;
+        return DONT_CARE;
     }
 
     int32_t bufSize = static_cast<int32_t>(env->GetArrayLength(pixelBuffer));
@@ -338,7 +410,7 @@
 
     if (pixels == NULL) {
         jniThrowNullPointerException(env, "pixels");
-        return;
+        return DONT_CARE;
     }
 
     status_t err = produceFrame(anw, reinterpret_cast<uint8_t*>(pixels), width, height,
@@ -346,42 +418,42 @@
     env->ReleaseByteArrayElements(pixelBuffer, pixels, JNI_ABORT);
 
     if (err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Error while producing frame (error code %d)", err);
-        return;
+        ALOGE("%s: Error while producing frame %s (%d).", __FUNCTION__, strerror(-err), err);
+        return err;
     }
+    return NO_ERROR;
 }
 
-static void LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
         jint pixelFormat) {
     ALOGV("nativeSetSurfaceType");
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return;
+        return BAD_VALUE;
     }
     status_t err = native_window_set_buffers_format(anw.get(), pixelFormat);
     if (err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Error while setting surface format (error code %d)", err);
-        return;
+        ALOGE("%s: Error while setting surface format %s (%d).", __FUNCTION__, strerror(-err), err);
+        return err;
     }
+    return NO_ERROR;
 }
 
-static void LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
         jint width, jint height) {
     ALOGV("nativeSetSurfaceDimens");
     sp<ANativeWindow> anw;
     if ((anw = getNativeWindow(env, surface)) == NULL) {
         ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
-        return;
+        return BAD_VALUE;
     }
     status_t err = native_window_set_buffers_dimensions(anw.get(), width, height);
     if (err != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Error while setting surface format (error code %d)", err);
-        return;
+        ALOGE("%s: Error while setting surface dimens %s (%d).", __FUNCTION__, strerror(-err), err);
+        return err;
     }
+    return NO_ERROR;
 }
 
 } // extern "C"
@@ -391,19 +463,19 @@
     "(Landroid/view/Surface;)I",
     (void *)LegacyCameraDevice_nativeDetectSurfaceType },
     { "nativeDetectSurfaceDimens",
-    "(Landroid/view/Surface;[I)V",
+    "(Landroid/view/Surface;[I)I",
     (void *)LegacyCameraDevice_nativeDetectSurfaceDimens },
     { "nativeConfigureSurface",
-    "(Landroid/view/Surface;III)V",
+    "(Landroid/view/Surface;III)I",
     (void *)LegacyCameraDevice_nativeConfigureSurface },
     { "nativeProduceFrame",
-    "(Landroid/view/Surface;[BIII)V",
+    "(Landroid/view/Surface;[BIII)I",
     (void *)LegacyCameraDevice_nativeProduceFrame },
     { "nativeSetSurfaceFormat",
-    "(Landroid/view/Surface;I)V",
+    "(Landroid/view/Surface;I)I",
     (void *)LegacyCameraDevice_nativeSetSurfaceFormat },
     { "nativeSetSurfaceDimens",
-    "(Landroid/view/Surface;II)V",
+    "(Landroid/view/Surface;II)I",
     (void *)LegacyCameraDevice_nativeSetSurfaceDimens },
 };
 
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index a2b1ed9..807dd32 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -20,11 +20,15 @@
 #include <system/audio.h>
 
 // keep these values in sync with AudioFormat.java
-#define ENCODING_PCM_16BIT 2
-#define ENCODING_PCM_8BIT  3
-#define ENCODING_PCM_FLOAT 4
-#define ENCODING_INVALID 0
-#define ENCODING_DEFAULT 1
+#define ENCODING_PCM_16BIT  2
+#define ENCODING_PCM_8BIT   3
+#define ENCODING_PCM_FLOAT  4
+#define ENCODING_AC3        5
+#define ENCODING_E_AC3      6
+#define ENCODING_INVALID    0
+#define ENCODING_DEFAULT    1
+
+
 
 #define CHANNEL_INVALID 0
 #define CHANNEL_OUT_DEFAULT 1
@@ -38,6 +42,10 @@
         return AUDIO_FORMAT_PCM_8_BIT;
     case ENCODING_PCM_FLOAT:
         return AUDIO_FORMAT_PCM_FLOAT;
+    case ENCODING_AC3:
+        return AUDIO_FORMAT_AC3;
+    case ENCODING_E_AC3:
+        return AUDIO_FORMAT_E_AC3;
     case ENCODING_DEFAULT:
         return AUDIO_FORMAT_DEFAULT;
     default:
@@ -54,6 +62,10 @@
         return ENCODING_PCM_8BIT;
     case AUDIO_FORMAT_PCM_FLOAT:
         return ENCODING_PCM_FLOAT;
+    case AUDIO_FORMAT_AC3:
+        return ENCODING_AC3;
+    case AUDIO_FORMAT_E_AC3:
+        return ENCODING_E_AC3;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
     default:
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index bf47dd3..849531c 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);
@@ -281,6 +287,8 @@
     env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz,
                               "errorCallbackFromNative","(I)V"),
                               check_AudioSystem_Command(err));
+
+    env->DeleteLocalRef(clazz);
 }
 
 static jint
@@ -1295,6 +1303,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_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index e548e91..677c230 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -40,14 +40,23 @@
 
 // ----------------------------------------------------------------------------
 static const char* const kClassPathName = "android/media/AudioTrack";
+static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
 
-struct fields_t {
+struct audio_track_fields_t {
     // these fields provide access from C++ to the...
     jmethodID postNativeEventInJava; //... event post callback method
     jfieldID  nativeTrackInJavaObj;  // stores in Java the native AudioTrack object
     jfieldID  jniData;      // stores in Java additional resources used by the native AudioTrack
+    jfieldID  fieldStreamType; // ... mStreamType field in the AudioTrack Java object
 };
-static fields_t javaAudioTrackFields;
+struct audio_attributes_fields_t {
+    jfieldID  fieldUsage;       // AudioAttributes.mUsage
+    jfieldID  fieldContentType; // AudioAttributes.mContentType
+    jfieldID  fieldFlags;       // AudioAttributes.mFlags
+    jfieldID  fieldTags;        // AudioAttributes.mTags
+};
+static audio_track_fields_t      javaAudioTrackFields;
+static audio_attributes_fields_t javaAudioAttrFields;
 
 struct audiotrack_callback_cookie {
     jclass      audioTrack_class;
@@ -66,12 +75,10 @@
         sp<MemoryHeapBase>         mMemHeap;
         sp<MemoryBase>             mMemBase;
         audiotrack_callback_cookie mCallbackData;
-        audio_stream_type_t        mStreamType;
 
     AudioTrackJniStorage() {
         mCallbackData.audioTrack_class = 0;
         mCallbackData.audioTrack_ref = 0;
-        mStreamType = AUDIO_STREAM_DEFAULT;
     }
 
     ~AudioTrackJniStorage() {
@@ -174,16 +181,21 @@
     env->SetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (jlong)at.get());
     return old;
 }
-
 // ----------------------------------------------------------------------------
 static jint
 android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-        jint streamType, jint sampleRateInHertz, jint javaChannelMask,
-        jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession)
-{
+        jobject jaa,
+        jint sampleRateInHertz, jint javaChannelMask,
+        jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) {
+
     ALOGV("sampleRate=%d, audioFormat(from Java)=%d, channel mask=%x, buffSize=%d",
         sampleRateInHertz, audioFormat, javaChannelMask, buffSizeInBytes);
 
+    if (jaa == 0) {
+        ALOGE("Error creating AudioTrack: invalid audio attributes");
+        return (jint) AUDIO_JAVA_ERROR;
+    }
+
     // Java channel masks don't map directly to the native definition, but it's a simple shift
     // to skip the two deprecated channel configurations "default" and "mono".
     audio_channel_mask_t nativeChannelMask = ((uint32_t)javaChannelMask) >> 2;
@@ -195,9 +207,6 @@
 
     uint32_t channelCount = popcount(nativeChannelMask);
 
-    // stream type already checked in Java
-    audio_stream_type_t atStreamType = (audio_stream_type_t) streamType;
-
     // check the format.
     // This function was called from Java, so we compare the format against the Java constants
     audio_format_t format = audioFormatToNative(audioFormat);
@@ -220,8 +229,13 @@
     }
 
     // compute the frame count
-    const size_t bytesPerSample = audio_bytes_per_sample(format);
-    size_t frameCount = buffSizeInBytes / (channelCount * bytesPerSample);
+    size_t frameCount;
+    if (audio_is_linear_pcm(format)) {
+        const size_t bytesPerSample = audio_bytes_per_sample(format);
+        frameCount = buffSizeInBytes / (channelCount * bytesPerSample);
+    } else {
+        frameCount = buffSizeInBytes;
+    }
 
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
@@ -246,10 +260,25 @@
     // create the native AudioTrack object
     sp<AudioTrack> lpTrack = new AudioTrack();
 
+    audio_attributes_t *paa = NULL;
+    // read the AudioAttributes values
+    paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
+    const jstring jtags    = (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldTags);
+    const char* tags = env->GetStringUTFChars(jtags, NULL);
+    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
+    strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
+    env->ReleaseStringUTFChars(jtags, tags);
+    paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
+    paa->content_type =
+            (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
+    paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
+
+    ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s",
+            paa->usage, paa->content_type, paa->flags, paa->tags);
+
     // initialize the callback information:
     // this data will be passed with every AudioTrack callback
     AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
-    lpJniStorage->mStreamType = atStreamType;
     lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
     // we use a weak reference so the AudioTrack object can be garbage collected.
     lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
@@ -261,17 +290,21 @@
     case MODE_STREAM:
 
         status = lpTrack->set(
-            atStreamType,// stream type
-            sampleRateInHertz,
-            format,// word length, PCM
-            nativeChannelMask,
-            frameCount,
-            AUDIO_OUTPUT_FLAG_NONE,
-            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
-            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-            0,// shared mem
-            true,// thread can call Java
-            sessionId);// audio session ID
+                AUDIO_STREAM_DEFAULT,// stream type
+                sampleRateInHertz,
+                format,// word length, PCM
+                nativeChannelMask,
+                frameCount,
+                AUDIO_OUTPUT_FLAG_NONE,
+                audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
+                0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
+                0,// shared mem
+                true,// thread can call Java
+                sessionId,// audio session ID
+                AudioTrack::TRANSFER_DEFAULT, // default transfer mode
+                NULL,                         // default offloadInfo
+                -1, -1,                       // default uid, pid values
+                paa);
         break;
 
     case MODE_STATIC:
@@ -283,17 +316,21 @@
         }
 
         status = lpTrack->set(
-            atStreamType,// stream type
-            sampleRateInHertz,
-            format,// word length, PCM
-            nativeChannelMask,
-            frameCount,
-            AUDIO_OUTPUT_FLAG_NONE,
-            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
-            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-            lpJniStorage->mMemBase,// shared mem
-            true,// thread can call Java
-            sessionId);// audio session ID
+                AUDIO_STREAM_DEFAULT,// stream type
+                sampleRateInHertz,
+                format,// word length, PCM
+                nativeChannelMask,
+                frameCount,
+                AUDIO_OUTPUT_FLAG_NONE,
+                audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
+                0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
+                lpJniStorage->mMemBase,// shared mem
+                true,// thread can call Java
+                sessionId,// audio session ID
+                AudioTrack::TRANSFER_DEFAULT, // default transfer mode
+                NULL,                         // default offloadInfo
+                -1, -1,                       // default uid, pid values
+                paa);
         break;
 
     default:
@@ -328,10 +365,21 @@
     //ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage);
     env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage);
 
+    // since we had audio attributes, the stream type was derived from them during the
+    // creation of the native AudioTrack: push the same value to the Java object
+    env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType());
+    // audio attributes were copied in AudioTrack creation
+    free(paa);
+    paa = NULL;
+
+
     return (jint) AUDIO_JAVA_SUCCESS;
 
     // failures:
 native_init_failure:
+    if (paa != NULL) {
+        free(paa);
+    }
     if (nSession != NULL) {
         env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
     }
@@ -478,14 +526,6 @@
         switch (format) {
 
         default:
-            // TODO Currently the only possible values for format are AUDIO_FORMAT_PCM_16_BIT,
-            // AUDIO_FORMAT_PCM_8_BIT, and AUDIO_FORMAT_PCM_FLOAT,
-            // due to the limited set of values for audioFormat.
-            // The next section of the switch will probably work for more formats, but it has only
-            // been tested for AUDIO_FORMAT_PCM_16_BIT and AUDIO_FORMAT_PCM_FLOAT,
-            // so that's why the "default" case fails.
-            break;
-
         case AUDIO_FORMAT_PCM_FLOAT:
         case AUDIO_FORMAT_PCM_16_BIT: {
             // writing to shared memory, check for capacity
@@ -904,8 +944,12 @@
         return -1;
     }
     const audio_format_t format = audioFormatToNative(audioFormat);
-    const size_t bytesPerSample = audio_bytes_per_sample(format);
-    return frameCount * channelCount * bytesPerSample;
+    if (audio_is_linear_pcm(format)) {
+        const size_t bytesPerSample = audio_bytes_per_sample(format);
+        return frameCount * channelCount * bytesPerSample;
+    } else {
+        return frameCount;
+    }
 }
 
 // ----------------------------------------------------------------------------
@@ -947,7 +991,7 @@
     {"native_stop",          "()V",      (void *)android_media_AudioTrack_stop},
     {"native_pause",         "()V",      (void *)android_media_AudioTrack_pause},
     {"native_flush",         "()V",      (void *)android_media_AudioTrack_flush},
-    {"native_setup",         "(Ljava/lang/Object;IIIIII[I)I",
+    {"native_setup",     "(Ljava/lang/Object;Ljava/lang/Object;IIIII[I)I",
                                          (void *)android_media_AudioTrack_setup},
     {"native_finalize",      "()V",      (void *)android_media_AudioTrack_finalize},
     {"native_release",       "()V",      (void *)android_media_AudioTrack_release},
@@ -991,6 +1035,7 @@
 #define JAVA_POSTEVENT_CALLBACK_NAME                    "postEventFromNative"
 #define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME            "mNativeTrackInJavaObj"
 #define JAVA_JNIDATA_FIELD_NAME                         "mJniData"
+#define JAVA_STREAMTYPE_FIELD_NAME                      "mStreamType"
 
 // ----------------------------------------------------------------------------
 // preconditions:
@@ -1040,7 +1085,7 @@
         ALOGE("Can't find AudioTrack.%s", JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME);
         return -1;
     }
-    //      jniData;
+    //      jniData
     javaAudioTrackFields.jniData = env->GetFieldID(
             audioTrackClass,
             JAVA_JNIDATA_FIELD_NAME, "J");
@@ -1048,6 +1093,33 @@
         ALOGE("Can't find AudioTrack.%s", JAVA_JNIDATA_FIELD_NAME);
         return -1;
     }
+    //      fieldStreamType
+    javaAudioTrackFields.fieldStreamType = env->GetFieldID(audioTrackClass,
+            JAVA_STREAMTYPE_FIELD_NAME, "I");
+    if (javaAudioTrackFields.fieldStreamType == NULL) {
+        ALOGE("Can't find AudioTrack.%s", JAVA_STREAMTYPE_FIELD_NAME);
+        return -1;
+    }
+
+    // Get the AudioAttributes class and fields
+    jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
+    if (audioAttrClass == NULL) {
+        ALOGE("Can't find %s", kAudioAttributesClassPathName);
+        return -1;
+    }
+    jclass audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
+    javaAudioAttrFields.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I");
+    javaAudioAttrFields.fieldContentType
+                                   = env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
+    javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I");
+    javaAudioAttrFields.fieldTags  = env->GetFieldID(audioAttributesClassRef, "mFormattedTags",
+            "Ljava/lang/String;");
+    env->DeleteGlobalRef(audioAttributesClassRef);
+    if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL
+            || javaAudioAttrFields.fieldFlags == NULL || javaAudioAttrFields.fieldTags == NULL) {
+        ALOGE("Can't initialize AudioAttributes fields");
+        return -1;
+    }
 
     return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index bc5e1b3..6f89800 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -214,7 +214,8 @@
     return android_net_utils_runDhcpCommon(env, clazz, ifname, info, false);
 }
 
-static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname,
+        jobject info)
 {
     return android_net_utils_runDhcpCommon(env, clazz, ifname, info, true);
 }
@@ -252,14 +253,14 @@
     }
 }
 
-static void android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
 {
-    setNetworkForProcess(netId);
+    return (jboolean) !setNetworkForProcess(netId);
 }
 
-static void android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
+static jboolean android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
 {
-    setNetworkForProcess(NETID_UNSET);
+    return (jboolean) !setNetworkForProcess(NETID_UNSET);
 }
 
 static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
@@ -267,19 +268,21 @@
     return getNetworkForProcess();
 }
 
-static void android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
+        jint netId)
 {
-    setNetworkForResolv(netId);
+    return (jboolean) !setNetworkForResolv(netId);
 }
 
-static void android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
+static jboolean android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
 {
-    setNetworkForResolv(NETID_UNSET);
+    return (jboolean) !setNetworkForResolv(NETID_UNSET);
 }
 
-static void android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, jint netId)
+static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
+        jint netId)
 {
-    setNetworkForSocket(netId, socket);
+    return (jboolean) !setNetworkForSocket(netId, socket);
 }
 
 // ----------------------------------------------------------------------------
@@ -299,12 +302,12 @@
     { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
     { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
     { "markSocket", "(II)V", (void*) android_net_utils_markSocket },
-    { "bindProcessToNetwork", "(I)V", (void*) android_net_utils_bindProcessToNetwork },
+    { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
-    { "unbindProcessToNetwork", "()V", (void*) android_net_utils_unbindProcessToNetwork },
-    { "bindProcessToNetworkForHostResolution", "(I)V", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "unbindProcessToNetworkForHostResolution", "()V", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
-    { "bindSocketToNetwork", "(II)V", (void*) android_net_utils_bindSocketToNetwork },
+    { "unbindProcessToNetwork", "()Z", (void*) android_net_utils_unbindProcessToNetwork },
+    { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
+    { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
+    { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 86207f0..87ee618 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -174,21 +174,21 @@
 
     ssize_t pss = memtrack_proc_graphics_pss(p);
     if (pss < 0) {
-        ALOGW("failed to get graphics pss: %d", pss);
+        ALOGW("failed to get graphics pss: %zd", pss);
         return pss;
     }
     graphics_mem->graphics = pss / 1024;
 
     pss = memtrack_proc_gl_pss(p);
     if (pss < 0) {
-        ALOGW("failed to get gl pss: %d", pss);
+        ALOGW("failed to get gl pss: %zd", pss);
         return pss;
     }
     graphics_mem->gl = pss / 1024;
 
     pss = memtrack_proc_other_pss(p);
     if (pss < 0) {
-        ALOGW("failed to get other pss: %d", pss);
+        ALOGW("failed to get other pss: %zd", pss);
         return pss;
     }
     graphics_mem->other = pss / 1024;
@@ -231,9 +231,9 @@
     unsigned referenced = 0;
     unsigned temp;
 
-    unsigned long int start;
-    unsigned long int end = 0;
-    unsigned long int prevEnd = 0;
+    uint64_t start;
+    uint64_t end = 0;
+    uint64_t prevEnd = 0;
     char* name;
     int name_pos;
 
@@ -255,7 +255,7 @@
         if (len < 1) return;
         line[--len] = 0;
 
-        if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
+        if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
             skip = true;
         } else {
             while (isspace(line[name_pos])) {
@@ -371,7 +371,7 @@
                 referenced = temp;
             } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) {
                 swapped_out = temp;
-            } else if (strlen(line) > 30 && line[8] == '-' && line[17] == ' ') {
+            } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) {
                 // looks like a new mapping
                 // example: "10000000-10001000 ---p 10000000 00:00 0"
                 break;
diff --git a/core/jni/android_server_FingerprintManager.cpp b/core/jni/android_server_FingerprintManager.cpp
index f8a1fd9..b174d1b 100644
--- a/core/jni/android_server_FingerprintManager.cpp
+++ b/core/jni/android_server_FingerprintManager.cpp
@@ -20,30 +20,10 @@
 
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
+#include <hardware/hardware.h>
+#include <hardware/fingerprint.h>
 #include <utils/Log.h>
 
-namespace android {
-
-static struct {
-    jclass clazz;
-    jmethodID notify;
-} gFingerprintManagerClassInfo;
-
-static jint nativeEnroll(JNIEnv* env, jobject clazz, jint timeout) {
-    return -1; // TODO
-}
-
-static jint nativeRemove(JNIEnv* env, jobject clazz, jint fingerprintId) {
-    return -1; // TODO
-}
-
-// ----------------------------------------------------------------------------
-
-static const JNINativeMethod g_methods[] = {
-    { "nativeEnroll", "(I)I", (void*)nativeEnroll },
-    { "nativeRemove", "(I)I", (void*)nativeRemove },
-};
-
 #define FIND_CLASS(var, className) \
         var = env->FindClass(className); \
         LOG_FATAL_IF(! var, "Unable to find class " className); \
@@ -61,13 +41,164 @@
         var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
         LOG_FATAL_IF(! var, "Unable to find field " fieldName);
 
-int register_android_server_FingerprintManager(JNIEnv* env) {
-    FIND_CLASS(gFingerprintManagerClassInfo.clazz,
-            "android/service/fingerprint/FingerprintManager");
-    GET_METHOD_ID(gFingerprintManagerClassInfo.notify, gFingerprintManagerClassInfo.clazz,
-            "notify", "(III)V");
-    return AndroidRuntime::registerNativeMethods(
-        env, "com/android/service/fingerprint/FingerprintManager", g_methods, NELEM(g_methods));
+namespace android {
+
+static const uint16_t kVersion = HARDWARE_MODULE_API_VERSION(1, 0);
+
+static const char* FINGERPRINT_SERVICE = "com/android/server/fingerprint/FingerprintService";
+static struct {
+    jclass clazz;
+    jmethodID notify;
+    jobject callbackObject;
+} gFingerprintServiceClassInfo;
+
+static struct {
+    fingerprint_module_t const* module;
+    fingerprint_device_t *device;
+} gContext;
+
+// Called by the HAL to notify us of fingerprint events
+static void hal_notify_callback(fingerprint_msg_t msg) {
+    uint32_t arg1 = 0;
+    uint32_t arg2 = 0;
+    uint32_t arg3 = 0; // TODO
+    switch (msg.type) {
+        case FINGERPRINT_ERROR:
+            arg1 = msg.data.error;
+            break;
+        case FINGERPRINT_ACQUIRED:
+            arg1 = msg.data.acquired.acquired_info;
+            break;
+        case FINGERPRINT_PROCESSED:
+            arg1 = msg.data.processed.id;
+            break;
+        case FINGERPRINT_TEMPLATE_ENROLLING:
+            arg1 = msg.data.enroll.id;
+            arg2 = msg.data.enroll.samples_remaining;
+            arg3 = msg.data.enroll.data_collected_bmp;
+            break;
+        case FINGERPRINT_TEMPLATE_REMOVED:
+            arg1 = msg.data.removed.id;
+            break;
+        default:
+            ALOGE("fingerprint: invalid msg: %d", msg.type);
+            return;
+    }
+    //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2);
+
+	// TODO: fix gross hack to attach JNI to calling thread
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
+        JavaVM* vm = AndroidRuntime::getJavaVM();
+        int result = vm->AttachCurrentThread(&env, (void*) &args);
+        if (result != JNI_OK) {
+            ALOGE("Can't call JNI method: attach failed: %#x", result);
+            return;
+        }
+    }
+    env->CallVoidMethod(gFingerprintServiceClassInfo.callbackObject,
+            gFingerprintServiceClassInfo.notify, msg.type, arg1, arg2);
+}
+
+static void nativeInit(JNIEnv *env, jobject clazz, jobject callbackObj) {
+    ALOG(LOG_VERBOSE, LOG_TAG, "nativeInit()\n");
+    FIND_CLASS(gFingerprintServiceClassInfo.clazz, FINGERPRINT_SERVICE);
+    GET_METHOD_ID(gFingerprintServiceClassInfo.notify, gFingerprintServiceClassInfo.clazz,
+           "notify", "(III)V");
+    gFingerprintServiceClassInfo.callbackObject = env->NewGlobalRef(callbackObj);
+}
+
+static jint nativeEnroll(JNIEnv* env, jobject clazz, jint timeout) {
+    ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll()\n");
+    int ret = gContext.device->enroll(gContext.device, timeout);
+    return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeEnrollCancel(JNIEnv* env, jobject clazz) {
+    ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnrollCancel()\n");
+    int ret = gContext.device->enroll_cancel(gContext.device);
+    return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeRemove(JNIEnv* env, jobject clazz, jint fingerprintId) {
+    ALOG(LOG_VERBOSE, LOG_TAG, "nativeRemove(%d)\n", fingerprintId);
+    int ret = gContext.device->remove(gContext.device, fingerprintId);
+    return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeOpenHal(JNIEnv* env, jobject clazz) {
+    ALOG(LOG_VERBOSE, LOG_TAG, "nativeOpenHal()\n");
+    int err;
+    const hw_module_t *hw_module = NULL;
+    if (0 != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) {
+        ALOGE("Can't open fingerprint HW Module, error: %d", err);
+        return 0;
+    }
+    if (NULL == hw_module) {
+        ALOGE("No valid fingerprint module");
+        return 0;
+    }
+
+    gContext.module = reinterpret_cast<const fingerprint_module_t*>(hw_module);
+
+    if (gContext.module->common.methods->open == NULL) {
+        ALOGE("No valid open method");
+        return 0;
+    }
+
+    hw_device_t *device = NULL;
+
+    if (0 != (err = gContext.module->common.methods->open(hw_module, NULL, &device))) {
+        ALOGE("Can't open fingerprint methods, error: %d", err);
+        return 0;
+    }
+
+    if (kVersion != device->version) {
+        ALOGE("Wrong fp version. Expected %d, got %d", kVersion, device->version);
+        // return 0; // FIXME
+    }
+
+    gContext.device = reinterpret_cast<fingerprint_device_t*>(device);
+    err = gContext.device->set_notify(gContext.device, hal_notify_callback);
+    if (err < 0) {
+        ALOGE("Failed in call to set_notify(), err=%d", err);
+        return 0;
+    }
+
+    // Sanity check - remove
+    if (gContext.device->notify != hal_notify_callback) {
+        ALOGE("NOTIFY not set properly: %p != %p", gContext.device->notify, hal_notify_callback);
+    }
+
+    ALOG(LOG_VERBOSE, LOG_TAG, "fingerprint HAL successfully initialized");
+    return reinterpret_cast<jlong>(gContext.device);
+}
+
+static jint nativeCloseHal(JNIEnv* env, jobject clazz) {
+    return -ENOSYS; // TODO
+}
+
+// ----------------------------------------------------------------------------
+
+// TODO: clean up void methods
+static const JNINativeMethod g_methods[] = {
+    { "nativeEnroll", "(I)I", (void*)nativeEnroll },
+    { "nativeEnrollCancel", "()I", (void*)nativeEnroll },
+    { "nativeRemove", "(I)I", (void*)nativeRemove },
+    { "nativeOpenHal", "()I", (void*)nativeOpenHal },
+    { "nativeCloseHal", "()I", (void*)nativeCloseHal },
+    { "nativeInit", "(Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit }
+};
+
+int register_android_server_fingerprint_FingerprintService(JNIEnv* env) {
+    FIND_CLASS(gFingerprintServiceClassInfo.clazz, FINGERPRINT_SERVICE);
+    GET_METHOD_ID(gFingerprintServiceClassInfo.notify, gFingerprintServiceClassInfo.clazz, "notify",
+            "(III)V");
+    int result = AndroidRuntime::registerNativeMethods(
+        env, FINGERPRINT_SERVICE, g_methods, NELEM(g_methods));
+    ALOG(LOG_VERBOSE, LOG_TAG, "FingerprintManager JNI ready.\n");
+    return result;
 }
 
 } // namespace android
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 01f4d3a..a6b65cc 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -45,6 +45,8 @@
 #define POLICY_DEBUG 0
 #define GUARD_THREAD_PRIORITY 0
 
+#define DEBUG_PROC(x) //x
+
 using namespace android;
 
 #if GUARD_THREAD_PRIORITY
@@ -725,6 +727,7 @@
         const char term = (char)(mode&PROC_TERM_MASK);
         const jsize start = i;
         if (i >= endIndex) {
+            DEBUG_PROC(ALOGW("Ran off end of data @%d", i));
             res = JNI_FALSE;
             break;
         }
@@ -822,19 +825,20 @@
         return JNI_FALSE;
     }
     int fd = open(file8, O_RDONLY);
-    env->ReleaseStringUTFChars(file, file8);
 
     if (fd < 0) {
-        //ALOGW("Unable to open process file: %s\n", file8);
+        DEBUG_PROC(ALOGW("Unable to open process file: %s\n", file8));
+        env->ReleaseStringUTFChars(file, file8);
         return JNI_FALSE;
     }
+    env->ReleaseStringUTFChars(file, file8);
 
     char buffer[256];
     const int len = read(fd, buffer, sizeof(buffer)-1);
     close(fd);
 
     if (len < 0) {
-        //ALOGW("Unable to open process file: %s fd=%d\n", file8, fd);
+        DEBUG_PROC(ALOGW("Unable to open process file: %s fd=%d\n", file8, fd));
         return JNI_FALSE;
     }
     buffer[len] = 0;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index d032cb6..b74bf0f 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -20,15 +20,10 @@
 #include "GraphicsJNI.h"
 #include <nativehelper/JNIHelp.h>
 
-#include "android_view_GraphicBuffer.h"
-
 #include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_graphics_SurfaceTexture.h>
 
 #include <androidfw/ResourceTypes.h>
 
-#include <gui/GLConsumer.h>
-
 #include <private/hwui/DrawGlInfo.h>
 
 #include <cutils/properties.h>
@@ -37,34 +32,27 @@
 #include <SkCanvas.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
+#include <SkPorterDuff.h>
 #include <SkRegion.h>
 #include <SkScalerContext.h>
 #include <SkTemplates.h>
 #include <SkXfermode.h>
 
 #include <DisplayListRenderer.h>
-#include <LayerRenderer.h>
-#include <OpenGLRenderer.h>
-#include <Stencil.h>
 #include <Rect.h>
 #include <RenderNode.h>
 #include <CanvasProperty.h>
 
-#ifdef USE_MINIKIN
 #include <minikin/Layout.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
-#endif
-
-#include <TextLayout.h>
-#include <TextLayoutCache.h>
 
 namespace android {
 
 using namespace uirenderer;
 
 /**
- * Note: OpenGLRenderer JNI layer is generated and compiled only on supported
+ * Note: DisplayListRenderer 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.
  */
@@ -91,57 +79,13 @@
 } 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);
-    RENDERER_LOGD("Destroy OpenGLRenderer");
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
+    RENDERER_LOGD("Destroy DisplayListRenderer");
     delete renderer;
 }
 
@@ -151,33 +95,29 @@
 
 static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint width, jint height) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->setViewport(width, height);
 }
 
 static int android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jboolean opaque) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return renderer->prepare(opaque);
 }
 
 static int android_view_GLES20Canvas_prepareDirty(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint left, jint top, jint right, jint bottom,
         jboolean opaque) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return renderer->prepareDirty(left, top, right, bottom, opaque);
 }
 
 static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     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()) {
@@ -202,7 +142,7 @@
 
 static jint android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong functorPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     Functor* functor = reinterpret_cast<Functor*>(functorPtr);
     android::uirenderer::Rect dirty;
     return renderer->callDrawGLFunction(functor, dirty);
@@ -226,25 +166,25 @@
 
 static jint android_view_GLES20Canvas_save(JNIEnv* env, jobject clazz, jlong rendererPtr,
         jint flags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return renderer->save(flags);
 }
 
 static jint android_view_GLES20Canvas_getSaveCount(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return renderer->getSaveCount();
 }
 
 static void android_view_GLES20Canvas_restore(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->restore();
 }
 
 static void android_view_GLES20Canvas_restoreToCount(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint saveCount) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->restoreToCount(saveCount);
 }
 
@@ -255,14 +195,14 @@
 static jint android_view_GLES20Canvas_saveLayer(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jlong paintPtr, jint saveFlags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     return renderer->saveLayer(left, top, right, bottom, paint, saveFlags);
 }
 
 static jint android_view_GLES20Canvas_saveLayerClip(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong paintPtr, jint saveFlags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds());
     return renderer->saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
@@ -272,13 +212,13 @@
 static jint android_view_GLES20Canvas_saveLayerAlpha(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jint alpha, jint saveFlags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     return renderer->saveLayerAlpha(left, top, right, bottom, alpha, saveFlags);
 }
 
 static jint android_view_GLES20Canvas_saveLayerAlphaClip(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint alpha, jint saveFlags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds());
     return renderer->saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom,
             alpha, saveFlags);
@@ -290,7 +230,7 @@
 
 static jboolean android_view_GLES20Canvas_quickReject(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const bool result = renderer->quickRejectConservative(left, top, right, bottom);
     return result ? JNI_TRUE : JNI_FALSE;
 }
@@ -298,7 +238,7 @@
 static jboolean android_view_GLES20Canvas_clipRectF(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jint op) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const bool result = renderer->clipRect(left, top, right, bottom,
                                            static_cast<SkRegion::Op>(op));
     return result ? JNI_TRUE : JNI_FALSE;
@@ -307,7 +247,7 @@
 static jboolean android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint left, jint top, jint right, jint bottom,
         jint op) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const bool result = renderer->clipRect(float(left), float(top), float(right),
                                            float(bottom),
                                            static_cast<SkRegion::Op>(op));
@@ -316,7 +256,7 @@
 
 static jboolean android_view_GLES20Canvas_clipPath(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong pathPtr, jint op) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPath* path = reinterpret_cast<SkPath*>(pathPtr);
     const bool result = renderer->clipPath(path, static_cast<SkRegion::Op>(op));
     return result ? JNI_TRUE : JNI_FALSE;
@@ -324,7 +264,7 @@
 
 static jboolean android_view_GLES20Canvas_clipRegion(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong regionPtr, jint op) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr);
     const bool result = renderer->clipRegion(region, static_cast<SkRegion::Op>(op));
     return result ? JNI_TRUE : JNI_FALSE;
@@ -332,7 +272,7 @@
 
 static jboolean android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jobject rect) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds());
 
     env->CallVoidMethod(rect, gRectClassInfo.set,
@@ -347,47 +287,47 @@
 
 static void android_view_GLES20Canvas_translate(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat dx, jfloat dy) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->translate(dx, dy);
 }
 
 static void android_view_GLES20Canvas_rotate(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat degrees) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->rotate(degrees);
 }
 
 static void android_view_GLES20Canvas_scale(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat sx, jfloat sy) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->scale(sx, sy);
 }
 
 static void android_view_GLES20Canvas_skew(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat sx, jfloat sy) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->skew(sx, sy);
 }
 
 static void android_view_GLES20Canvas_setMatrix(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong matrixPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(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,
         jlong rendererPtr, jlong matrixPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
     renderer->getMatrix(matrix);
 }
 
 static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong matrixPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-    renderer->concatMatrix(matrix);
+    renderer->concatMatrix(*matrix);
 }
 
 // ----------------------------------------------------------------------------
@@ -401,7 +341,7 @@
     // This object allows the renderer to allocate a global JNI ref to the buffer object.
     JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawBitmap(bitmap, left, top, paint);
 }
@@ -414,7 +354,7 @@
     // This object allows the renderer to allocate a global JNI ref to the buffer object.
     JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
             dstLeft, dstTop, dstRight, dstBottom, paint);
@@ -427,20 +367,20 @@
     // This object allows the renderer to allocate a global JNI ref to the buffer object.
     JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(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,
         jlong rendererPtr, jintArray colors, jint offset, jint stride,
         jfloat left, jfloat top, jint width, jint height, jboolean hasAlpha, jlong paintPtr) {
+    const SkImageInfo info = SkImageInfo::Make(width, height,
+                               hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+                               kPremul_SkAlphaType);
     SkBitmap* bitmap = new SkBitmap;
-    bitmap->setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config,
-            width, height);
-
-    if (!bitmap->allocPixels()) {
+    if (!bitmap->allocPixels(info)) {
         delete bitmap;
         return;
     }
@@ -450,7 +390,7 @@
         return;
     }
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawBitmapData(bitmap, left, top, paint);
 
@@ -471,7 +411,7 @@
     jfloat* verticesArray = vertices ? env->GetFloatArrayElements(vertices, NULL) + offset : NULL;
     jint* colorsArray = colors ? env->GetIntArrayElements(colors, NULL) + colorOffset : NULL;
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawBitmapMesh(bitmap, meshWidth, meshHeight, verticesArray, colorsArray, paint);
 
@@ -486,22 +426,23 @@
     // This object allows the renderer to allocate a global JNI ref to the buffer object.
     JavaHeapBitmapRef bitmapRef(env, bitmap, buffer);
 
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(patchPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
 }
 
 static void android_view_GLES20Canvas_drawColor(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jint color, jint mode) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    renderer->drawColor(color, static_cast<SkXfermode::Mode>(mode));
+        jlong rendererPtr, jint color, jint modeHandle) {
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
+    SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
+    renderer->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
 }
 
 static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawRect(left, top, right, bottom, paint);
 }
@@ -509,21 +450,21 @@
 static void android_view_GLES20Canvas_drawRoundRect(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jfloat rx, jfloat ry, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawRoundRect(left, top, right, bottom, rx, ry, paint);
 }
 
 static void android_view_GLES20Canvas_drawCircle(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat x, jfloat y, jfloat radius, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawCircle(x, y, radius, paint);
 }
 
 static void android_view_GLES20Canvas_drawCircleProps(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
     CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
     CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr);
@@ -534,7 +475,7 @@
 static void android_view_GLES20Canvas_drawOval(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawOval(left, top, right, bottom, paint);
 }
@@ -542,14 +483,14 @@
 static void android_view_GLES20Canvas_drawArc(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloat left, jfloat top, jfloat right, jfloat bottom,
         jfloat startAngle, jfloat sweepAngle, jboolean useCenter, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
 }
 
 static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong regionPtr, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     if (paint->getStyle() != SkPaint::kFill_Style ||
@@ -577,18 +518,9 @@
     }
 }
 
-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);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     jfloat* storage = env->GetFloatArrayElements(points, NULL);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawPoints(storage + offset, count, paint);
@@ -597,7 +529,7 @@
 
 static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong pathPtr, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     SkPath* path = reinterpret_cast<SkPath*>(pathPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawPath(path, paint);
@@ -605,7 +537,7 @@
 
 static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     jfloat* storage = env->GetFloatArrayElements(points, NULL);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     renderer->drawLines(storage + offset, count, paint);
@@ -618,13 +550,13 @@
 
 static void android_view_GLES20Canvas_setupPaintFilter(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint clearBits, jint setBits) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->setupPaintFilter(clearBits, setBits);
 }
 
 static void android_view_GLES20Canvas_resetPaintFilter(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     renderer->resetPaintFilter();
 }
 
@@ -632,26 +564,9 @@
 // Text
 // ----------------------------------------------------------------------------
 
-// TODO: this is moving to MinikinUtils, remove with USE_MINIKIN ifdef
-static float xOffsetForTextAlign(SkPaint* paint, float totalAdvance) {
-    switch (paint->getTextAlign()) {
-        case SkPaint::kCenter_Align:
-            return -totalAdvance / 2.0f;
-            break;
-        case SkPaint::kRight_Align:
-            return -totalAdvance;
-            break;
-        default:
-            break;
-    }
-    return 0;
-}
-
-#ifdef USE_MINIKIN
-
 class RenderTextFunctor {
 public:
-    RenderTextFunctor(const Layout& layout, OpenGLRenderer* renderer, jfloat x, jfloat y,
+    RenderTextFunctor(const Layout& layout, DisplayListRenderer* renderer, jfloat x, jfloat y,
                 SkPaint* paint, uint16_t* glyphs, float* pos, float totalAdvance,
                 uirenderer::Rect& bounds)
             : layout(layout), renderer(renderer), x(x), y(y), paint(paint), glyphs(glyphs),
@@ -669,7 +584,7 @@
     }
 private:
     const Layout& layout;
-    OpenGLRenderer* renderer;
+    DisplayListRenderer* renderer;
     jfloat x;
     jfloat y;
     SkPaint* paint;
@@ -679,7 +594,7 @@
     uirenderer::Rect& bounds;
 };
 
-static void renderTextLayout(OpenGLRenderer* renderer, Layout* layout,
+static void renderTextLayout(DisplayListRenderer* renderer, Layout* layout,
     jfloat x, jfloat y, SkPaint* paint) {
     size_t nGlyphs = layout->nGlyphs();
     float* pos = new float[nGlyphs * 2];
@@ -695,201 +610,149 @@
     delete[] glyphs;
     delete[] pos;
 }
-#endif
 
-static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
-        jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
+static void renderText(DisplayListRenderer* renderer, const jchar* text, int count,
+        jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
     layout.doLayout(text, 0, count, count, css);
-    x += xOffsetForTextAlign(paint, layout.getAdvance());
+    x += MinikinUtils::xOffsetForTextAlign(paint, layout);
     renderTextLayout(renderer, &layout, x, y, paint);
-#else
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count, flags);
-    if (value == NULL) {
-        return;
-    }
-    const jchar* glyphs = value->getGlyphs();
-    size_t glyphsCount = value->getGlyphsCount();
-    jfloat totalAdvance = value->getTotalAdvance();
-    x += xOffsetForTextAlign(paint, totalAdvance);
-    const float* positions = value->getPos();
-    int bytesCount = glyphsCount * sizeof(jchar);
-    const SkRect& r = value->getBounds();
-    android::uirenderer::Rect bounds(r.fLeft, r.fTop, r.fRight, r.fBottom);
-    bounds.translate(x, y);
-
-    renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
-            x, y, positions, paint, totalAdvance, bounds);
-#endif
 }
 
-static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count,
-        SkPath* path, jfloat hOffset, jfloat vOffset, int flags, SkPaint* paint) {
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count, flags);
-    if (value == NULL) {
-        return;
+class RenderTextOnPathFunctor {
+public:
+    RenderTextOnPathFunctor(const Layout& layout, DisplayListRenderer* renderer, float hOffset,
+                float vOffset, SkPaint* paint, SkPath* path)
+            : layout(layout), renderer(renderer), hOffset(hOffset), vOffset(vOffset),
+                paint(paint), path(path) {
     }
-    const jchar* glyphs = value->getGlyphs();
-    size_t glyphsCount = value->getGlyphsCount();
-    int bytesCount = glyphsCount * sizeof(jchar);
-    renderer->drawTextOnPath((const char*) glyphs, bytesCount, glyphsCount, path,
-            hOffset, vOffset, paint);
-}
+    void operator()(size_t start, size_t end) {
+        uint16_t glyphs[1];
+        for (size_t i = start; i < end; i++) {
+            glyphs[0] = layout.getGlyphId(i);
+            float x = hOffset + layout.getX(i);
+            float y = vOffset + layout.getY(i);
+            renderer->drawTextOnPath((const char*) glyphs, sizeof(glyphs), 1, path, x, y, paint);
+        }
+    }
+private:
+    const Layout& layout;
+    DisplayListRenderer* renderer;
+    float hOffset;
+    float vOffset;
+    SkPaint* paint;
+    SkPath* path;
+};
 
-static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
-        jint start, jint count, jint contextCount, jfloat x, jfloat y,
-        int flags, SkPaint* paint, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
+static void renderTextOnPath(DisplayListRenderer* renderer, const jchar* text, int count,
+        SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, SkPaint* paint,
+        TypefaceImpl* typeface) {
     Layout layout;
-    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
-    layout.doLayout(text, start, count, contextCount, css);
-    x += xOffsetForTextAlign(paint, layout.getAdvance());
-    renderTextLayout(renderer, &layout, x, y, paint);
-#else
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, start, count, contextCount, flags);
-    if (value == NULL) {
-        return;
-    }
-    const jchar* glyphs = value->getGlyphs();
-    size_t glyphsCount = value->getGlyphsCount();
-    jfloat totalAdvance = value->getTotalAdvance();
-    x += xOffsetForTextAlign(paint, totalAdvance);
-    const float* positions = value->getPos();
-    int bytesCount = glyphsCount * sizeof(jchar);
-    const SkRect& r = value->getBounds();
-    android::uirenderer::Rect bounds(r.fLeft, r.fTop, r.fRight, r.fBottom);
-    bounds.translate(x, y);
+    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+    layout.doLayout(text, 0, count, count, css);
+    hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path);
+    SkPaint::Align align = paint->getTextAlign();
+    paint->setTextAlign(SkPaint::kLeft_Align);
 
-    renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
-            x, y, positions, paint, totalAdvance, bounds);
-#endif
+    RenderTextOnPathFunctor f(layout, renderer, hOffset, vOffset, paint, path);
+    MinikinUtils::forFontRun(layout, paint, f);
+    paint->setTextAlign(align);
+}
+
+static void renderTextRun(DisplayListRenderer* renderer, const jchar* text,
+        jint start, jint count, jint contextCount, jfloat x, jfloat y,
+        int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
+    Layout layout;
+    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+    layout.doLayout(text, start, count, contextCount, css);
+    x += MinikinUtils::xOffsetForTextAlign(paint, layout);
+    renderTextLayout(renderer, &layout, x, y, paint);
 }
 
 static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jcharArray text, jint index, jint count,
-        jfloat x, jfloat y, jint flags, jlong paintPtr, jlong typefacePtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+        jfloat x, jfloat y, jint bidiFlags, jlong paintPtr, jlong typefacePtr) {
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
-    renderText(renderer, textArray + index, count, x, y, flags, paint, typeface);
+    renderText(renderer, textArray + index, count, x, y, bidiFlags, paint, typeface);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jstring text, jint start, jint end,
-        jfloat x, jfloat y, jint flags, jlong paintPtr, jlong typefacePtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+        jfloat x, jfloat y, jint bidiFlags, jlong paintPtr, jlong typefacePtr) {
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const jchar* textArray = env->GetStringChars(text, NULL);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
-    renderText(renderer, textArray + start, end - start, x, y, flags, paint, typeface);
+    renderText(renderer, textArray + start, end - start, x, y, bidiFlags, paint, typeface);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jcharArray text, jint index, jint count,
-        jlong pathPtr, jfloat hOffset, jfloat vOffset, jint flags, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+        jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr,
+        jlong typefacePtr) {
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     SkPath* path = reinterpret_cast<SkPath*>(pathPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
     renderTextOnPath(renderer, textArray + index, count, path,
-            hOffset, vOffset, flags, paint);
+            hOffset, vOffset, bidiFlags, paint, typeface);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jstring text, jint start, jint end,
-        jlong pathPtr, jfloat hOffset, jfloat vOffset, jint flags, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+        jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr,
+        jlong typefacePtr) {
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const jchar* textArray = env->GetStringChars(text, NULL);
     SkPath* path = reinterpret_cast<SkPath*>(pathPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
+    TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
     renderTextOnPath(renderer, textArray + start, end - start, path,
-            hOffset, vOffset, flags, paint);
+            hOffset, vOffset, bidiFlags, paint, typeface);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jcharArray text, jint index, jint count,
-        jint contextIndex, jint contextCount, jfloat x, jfloat y, jint dirFlags,
+        jint contextIndex, jint contextCount, jfloat x, jfloat y, jboolean isRtl,
         jlong paintPtr, jlong typefacePtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
+    int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
     renderTextRun(renderer, textArray + contextIndex, index - contextIndex,
-            count, contextCount, x, y, dirFlags, paint, typeface);
+            count, contextCount, x, y, bidiFlags, paint, typeface);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
  }
 
 static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jstring text, jint start, jint end,
-        jint contextStart, int contextEnd, jfloat x, jfloat y, jint dirFlags,
+        jint contextStart, int contextEnd, jfloat x, jfloat y, jboolean isRtl,
         jlong paintPtr, jlong typefacePtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     const jchar* textArray = env->GetStringChars(text, NULL);
     jint count = end - start;
     jint contextCount = contextEnd - contextStart;
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
     TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr);
 
+    int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
     renderTextRun(renderer, textArray + contextStart, start - contextStart,
-            count, contextCount, x, y, dirFlags, paint, typeface);
-    env->ReleaseStringChars(text, textArray);
-}
-
-static void renderPosText(OpenGLRenderer* renderer, const jchar* text, int count,
-        const jfloat* positions, jint dirFlags, SkPaint* paint) {
-    sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count, dirFlags);
-    if (value == NULL) {
-        return;
-    }
-    const jchar* glyphs = value->getGlyphs();
-    size_t glyphsCount = value->getGlyphsCount();
-    if (count < int(glyphsCount)) glyphsCount = count;
-    int bytesCount = glyphsCount * sizeof(jchar);
-
-    renderer->drawPosText((const char*) glyphs, bytesCount, glyphsCount, positions, paint);
-}
-
-static void android_view_GLES20Canvas_drawPosTextArray(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jcharArray text, jint index, jint count,
-        jfloatArray pos, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    jchar* textArray = env->GetCharArrayElements(text, NULL);
-    jfloat* positions = env->GetFloatArrayElements(pos, NULL);
-    SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
-
-    renderPosText(renderer, textArray + index, count, positions, kBidi_LTR, paint);
-
-    env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
-    env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
-}
-
-static void android_view_GLES20Canvas_drawPosText(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jstring text, jint start, jint end,
-        jfloatArray pos, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    const jchar* textArray = env->GetStringChars(text, NULL);
-    jfloat* positions = env->GetFloatArrayElements(pos, NULL);
-    SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
-
-    renderPosText(renderer, textArray + start, end - start, positions, kBidi_LTR, paint);
-
-    env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
+            count, contextCount, x, y, bidiFlags, paint, typeface);
     env->ReleaseStringChars(text, textArray);
 }
 
@@ -907,13 +770,13 @@
     return reinterpret_cast<jlong>(new DisplayListRenderer);
 }
 
-static jint android_view_GLES20Canvas_drawDisplayList(JNIEnv* env,
-        jobject clazz, jlong rendererPtr, jlong displayListPtr,
+static jint android_view_GLES20Canvas_drawRenderNode(JNIEnv* env,
+        jobject clazz, jlong rendererPtr, jlong renderNodePtr,
         jobject dirty, jint flags) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     android::uirenderer::Rect bounds;
-    status_t status = renderer->drawDisplayList(displayList, bounds, flags);
+    status_t status = renderer->drawRenderNode(renderNode, bounds, flags);
     if (status != DrawGlInfo::kStatusDone && dirty != NULL) {
         env->CallVoidMethod(dirty, gRectClassInfo.set,
                 int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom));
@@ -927,44 +790,11 @@
 
 static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong layerPtr, jfloat x, jfloat y) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
+    DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
     Layer* layer = reinterpret_cast<Layer*>(layerPtr);
     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 +839,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 +848,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 +887,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 },
@@ -1085,33 +904,22 @@
     { "nDrawText",          "(JLjava/lang/String;IIFFIJJ)V",
             (void*) android_view_GLES20Canvas_drawText },
 
-    { "nDrawTextOnPath",    "(J[CIIJFFIJ)V",   (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
-    { "nDrawTextOnPath",    "(JLjava/lang/String;IIJFFIJ)V",
+    { "nDrawTextOnPath",    "(J[CIIJFFIJJ)V",  (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
+    { "nDrawTextOnPath",    "(JLjava/lang/String;IIJFFIJJ)V",
             (void*) android_view_GLES20Canvas_drawTextOnPath },
 
-    { "nDrawTextRun",       "(J[CIIIIFFIJJ)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
-    { "nDrawTextRun",       "(JLjava/lang/String;IIIIFFIJJ)V",
+    { "nDrawTextRun",       "(J[CIIIIFFZJJ)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
+    { "nDrawTextRun",       "(JLjava/lang/String;IIIIFFZJJ)V",
             (void*) android_view_GLES20Canvas_drawTextRun },
 
-    { "nDrawPosText",       "(J[CII[FJ)V",     (void*) android_view_GLES20Canvas_drawPosTextArray },
-    { "nDrawPosText",       "(JLjava/lang/String;II[FJ)V",
-            (void*) android_view_GLES20Canvas_drawPosText },
+    { "nGetClipBounds",     "(JLandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_getClipBounds },
 
-    { "nGetClipBounds",     "(JLandroid/graphics/Rect;)Z",
-            (void*) android_view_GLES20Canvas_getClipBounds },
-
-    { "nFinishRecording",        "(J)J",      (void*) android_view_GLES20Canvas_finishRecording },
-    { "nDrawDisplayList",        "(JJLandroid/graphics/Rect;I)I",
-            (void*) android_view_GLES20Canvas_drawDisplayList },
+    { "nFinishRecording",   "(J)J",      (void*) android_view_GLES20Canvas_finishRecording },
+    { "nDrawRenderNode",    "(JJLandroid/graphics/Rect;I)I", (void*) android_view_GLES20Canvas_drawRenderNode },
 
     { "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..5ebed9c 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) \
@@ -141,16 +142,16 @@
 // Canvas management
 // ----------------------------------------------------------------------------
 
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
+static inline SkColorType convertPixelFormat(int32_t format) {
     switch (format) {
         case PIXEL_FORMAT_RGBA_8888:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
         case PIXEL_FORMAT_RGBX_8888:
-            return SkBitmap::kARGB_8888_Config;
+            return kN32_SkColorType;
         case PIXEL_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            return kRGB_565_SkColorType;
         default:
-            return SkBitmap::kNo_Config;
+            return kUnknown_SkColorType;
     }
 }
 
@@ -187,8 +188,10 @@
     ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
 
     SkBitmap bitmap;
-    bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()),
-            buffer->getWidth(), buffer->getHeight(), bytesCount);
+    bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
+                                     convertPixelFormat(buffer->getPixelFormat()),
+                                     kPremul_SkAlphaType),
+                   bytesCount);
 
     if (buffer->getWidth() > 0 && buffer->getHeight() > 0) {
         bitmap.setPixels(bits);
@@ -197,12 +200,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 +220,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 +320,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..ace17ec 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);
@@ -102,21 +87,6 @@
     layer->updateTexImage();
 }
 
-static void android_view_HardwareLayer_updateRenderLayer(JNIEnv* env, jobject clazz,
-        jlong layerUpdaterPtr, jlong displayListPtr,
-        jint left, jint top, jint right, jint bottom) {
-    DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
-    RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
-    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 +110,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 },
@@ -150,9 +118,6 @@
     { "nSetSurfaceTexture",      "(JLandroid/graphics/SurfaceTexture;Z)V",
             (void*) android_view_HardwareLayer_setSurfaceTexture },
     { "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 },
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 26022e0..6ba22bf 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,199 @@
 // RenderProperties - setters
 // ----------------------------------------------------------------------------
 
-static void 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);
+static jboolean android_view_RenderNode_setLayerType(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr, jint jlayerType) {
+    LayerType layerType = static_cast<LayerType>(jlayerType);
+    return SET_AND_DIRTY(mutateLayerProperties().setType, layerType, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setStaticMatrix(JNIEnv* env,
+static jboolean android_view_RenderNode_setLayerPaint(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr, jlong paintPtr) {
+    SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
+    return SET_AND_DIRTY(mutateLayerProperties().setFromPaint, paint, RenderNode::GENERIC);
+}
+
+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);
 }
 
 // ----------------------------------------------------------------------------
@@ -487,16 +456,9 @@
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
     renderNode->addAnimator(animator);
+    animator->start();
 }
 
-static void android_view_RenderNode_removeAnimator(JNIEnv* env, jobject clazz,
-        jlong renderNodePtr, jlong animatorPtr) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
-    renderNode->removeAnimator(animator);
-}
-
-
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -513,41 +475,42 @@
     { "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 },
+    { "nSetLayerType",         "(JI)Z",  (void*) android_view_RenderNode_setLayerType },
+    { "nSetLayerPaint",        "(JJ)Z",  (void*) android_view_RenderNode_setLayerPaint },
+    { "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 },
@@ -576,7 +539,6 @@
     { "nGetPivotY",                "(J)F",  (void*) android_view_RenderNode_getPivotY },
 
     { "nAddAnimator",              "(JJ)V", (void*) android_view_RenderNode_addAnimator },
-    { "nRemoveAnimator",           "(JJ)V", (void*) android_view_RenderNode_removeAnimator },
 #endif
 };
 
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index d689864..de3dd16 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -149,6 +149,11 @@
     animator->setInterpolator(interpolator);
 }
 
+static void cancel(JNIEnv* env, jobject clazz, jlong animatorPtr) {
+    BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
+    animator->cancel();
+}
+
 #endif
 
 // ----------------------------------------------------------------------------
@@ -168,6 +173,7 @@
     { "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
     { "nGetStartDelay", "(J)J", (void*) getStartDelay },
     { "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
+    { "nCancel", "(J)V", (void*) cancel },
 #endif
 };
 
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 6c9d060..7018751 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;
 }
 
@@ -172,17 +173,17 @@
     return value;
 }
 
-static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
+static inline SkColorType convertPixelFormat(PixelFormat format) {
     /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
-        we can map to SkBitmap::kARGB_8888_Config, and optionally call
+        we can map to kN32_SkColorType, and optionally call
         bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap
         (as an accelerator)
     */
     switch (format) {
-    case PIXEL_FORMAT_RGBX_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGBA_8888:    return SkBitmap::kARGB_8888_Config;
-    case PIXEL_FORMAT_RGB_565:      return SkBitmap::kRGB_565_Config;
-    default:                        return SkBitmap::kNo_Config;
+    case PIXEL_FORMAT_RGBX_8888:    return kN32_SkColorType;
+    case PIXEL_FORMAT_RGBA_8888:    return kN32_SkColorType;
+    case PIXEL_FORMAT_RGB_565:      return kRGB_565_SkColorType;
+    default:                        return kUnknown_SkColorType;
     }
 }
 
@@ -219,12 +220,16 @@
     // Associate a SkCanvas object to this surface
     env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format);
 
+    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
+                                         convertPixelFormat(outBuffer.format),
+                                         kPremul_SkAlphaType);
+    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
+        info.fAlphaType = kOpaque_SkAlphaType;
+    }
+
     SkBitmap bitmap;
     ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
-    bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
-    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
-        bitmap.setAlphaType(kOpaque_SkAlphaType);
-    }
+    bitmap.setInfo(info, bpr);
     if (outBuffer.width > 0 && outBuffer.height > 0) {
         bitmap.setPixels(outBuffer.bits);
     } else {
@@ -232,10 +237,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 +268,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 +380,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..9783e91 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 {
@@ -59,8 +61,17 @@
     jfieldID xDpi;
     jfieldID yDpi;
     jfieldID secure;
+    jfieldID appVsyncOffsetNanos;
+    jfieldID presentationDeadlineNanos;
 } 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 +115,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 +167,6 @@
             break;
         }
         default: {
-            delete screenshot;
             return NULL;
         }
     }
@@ -159,12 +175,12 @@
             screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
 
     SkBitmap* bitmap = new SkBitmap();
-    bitmap->setConfig(screenshotInfo, (size_t)rowBytes);
+    bitmap->setInfo(screenshotInfo, (size_t)rowBytes);
     if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) {
         // 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 +190,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);
         }
@@ -373,6 +394,10 @@
         env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.xDpi, info.xdpi);
         env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.yDpi, info.ydpi);
         env->SetBooleanField(infoObj, gPhysicalDisplayInfoClassInfo.secure, info.secure);
+        env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos,
+                info.appVsyncOffset);
+        env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos,
+                info.presentationDeadline);
         env->SetObjectArrayElement(configArray, static_cast<jsize>(c), infoObj);
         env->DeleteLocalRef(infoObj);
     }
@@ -393,20 +418,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 +580,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 +626,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 +634,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)
@@ -639,6 +654,16 @@
     gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F");
     gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F");
     gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z");
+    gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos = env->GetFieldID(clazz,
+            "appVsyncOffsetNanos", "J");
+    gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos = env->GetFieldID(clazz,
+            "presentationDeadlineNanos", "J");
+
+    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");
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 9258543..5c04a78 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 {
@@ -71,17 +73,28 @@
 // Native layer
 // ----------------------------------------------------------------------------
 
-static inline SkBitmap::Config convertPixelFormat(int32_t format) {
-    switch (format) {
+// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
+static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
+    SkImageInfo info;
+    info.fWidth = buffer.width;
+    info.fHeight = buffer.height;
+    switch (buffer.format) {
         case WINDOW_FORMAT_RGBA_8888:
-            return SkBitmap::kARGB_8888_Config;
+            info.fColorType = kN32_SkColorType;
+            info.fAlphaType = kPremul_SkAlphaType;
+            break;
         case WINDOW_FORMAT_RGBX_8888:
-            return SkBitmap::kARGB_8888_Config;
+            info.fColorType = kN32_SkColorType;
+            info.fAlphaType = kOpaque_SkAlphaType;
         case WINDOW_FORMAT_RGB_565:
-            return SkBitmap::kRGB_565_Config;
+            info.fColorType = kRGB_565_SkColorType;
+            info.fAlphaType = kOpaque_SkAlphaType;
         default:
-            return SkBitmap::kNo_Config;
+            info.fColorType = kUnknown_SkColorType;
+            info.fAlphaType = kIgnore_SkAlphaType;
+            break;
     }
+    return info;
 }
 
 /**
@@ -146,11 +159,7 @@
     ssize_t bytesCount = buffer.stride * bytesPerPixel(buffer.format);
 
     SkBitmap bitmap;
-    bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount);
-
-    if (buffer.format == WINDOW_FORMAT_RGBX_8888) {
-        bitmap.setAlphaType(kOpaque_SkAlphaType);
-    }
+    bitmap.setInfo(convertPixelFormat(buffer), bytesCount);
 
     if (buffer.width > 0 && buffer.height > 0) {
         bitmap.setPixels(buffer.bits);
@@ -159,12 +168,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 +186,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 +235,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..3e62d0b 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>
@@ -45,6 +49,14 @@
 
 static jmethodID gRunnableMethod;
 
+static JNIEnv* getenv(JavaVM* vm) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+    }
+    return env;
+}
+
 class JavaTask : public RenderTask {
 public:
     JavaTask(JNIEnv* env, jobject jrunnable) {
@@ -53,44 +65,17 @@
     }
 
     virtual void run() {
-        env()->CallVoidMethod(mRunnable, gRunnableMethod);
-        env()->DeleteGlobalRef(mRunnable);
+        JNIEnv* env = getenv(mVm);
+        env->CallVoidMethod(mRunnable, gRunnableMethod);
+        env->DeleteGlobalRef(mRunnable);
         delete this;
     };
 
 private:
-    JNIEnv* env() {
-        JNIEnv* env;
-        if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-            return 0;
-        }
-        return env;
-    }
-
     JavaVM* mVm;
     jobject mRunnable;
 };
 
-class SetAtlasTask : public RenderTask {
-public:
-    SetAtlasTask(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size)
-            : mBuffer(buffer)
-            , mMap(map)
-            , mMapSize(size) {
-    }
-
-    virtual void run() {
-        CanvasContext::setTextureAtlas(mBuffer, mMap, mMapSize);
-        mMap = 0;
-        delete this;
-    }
-
-private:
-    sp<GraphicBuffer> mBuffer;
-    int64_t* mMap;
-    size_t mMapSize;
-};
-
 class OnFinishedEvent {
 public:
     OnFinishedEvent(BaseRenderNodeAnimator* animator, AnimationListener* listener)
@@ -118,12 +103,34 @@
     std::vector<OnFinishedEvent> mOnFinishedEvents;
 };
 
-class RootRenderNode : public RenderNode, public AnimationHook {
+class RenderingException : public MessageHandler {
 public:
-    RootRenderNode() : RenderNode() {
+    RenderingException(JavaVM* vm, const std::string& message)
+            : mVm(vm)
+            , mMessage(message) {
+    }
+
+    virtual void handleMessage(const Message&) {
+        throwException(mVm, mMessage);
+    }
+
+    static void throwException(JavaVM* vm, const std::string& message) {
+        JNIEnv* env = getenv(vm);
+        jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
+    }
+
+private:
+    JavaVM* mVm;
+    std::string mMessage;
+};
+
+class RootRenderNode : public RenderNode, AnimationHook, ErrorHandler {
+public:
+    RootRenderNode(JNIEnv* env) : RenderNode() {
         mLooper = Looper::getForThread();
         LOG_ALWAYS_FATAL_IF(!mLooper.get(),
                 "Must create RootRenderNode on a thread with a looper!");
+        env->GetJavaVM(&mVm);
     }
 
     virtual ~RootRenderNode() {}
@@ -133,10 +140,16 @@
         mOnFinishedEvents.push_back(event);
     }
 
+    virtual void onError(const std::string& message) {
+        mLooper->sendMessage(new RenderingException(mVm, message), 0);
+    }
+
     virtual void prepareTree(TreeInfo& info) {
         info.animationHook = this;
+        info.errorHandler = this;
         RenderNode::prepareTree(info);
         info.animationHook = NULL;
+        info.errorHandler = NULL;
 
         // post all the finished stuff
         if (mOnFinishedEvents.size()) {
@@ -146,13 +159,21 @@
         }
     }
 
+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;
+    JavaVM* mVm;
 };
 
 static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
-        jobject graphicBuffer, jlongArray atlasMapArray) {
+        jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
     sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
     jsize len = env->GetArrayLength(atlasMapArray);
     if (len <= 0) {
@@ -162,12 +183,12 @@
     int64_t* map = new int64_t[len];
     env->GetLongArrayRegion(atlasMapArray, 0, len, map);
 
-    SetAtlasTask* task = new SetAtlasTask(buffer, map, len);
-    RenderThread::getInstance().queue(task);
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->setTextureAtlas(buffer, map, len);
 }
 
 static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
-    RootRenderNode* node = new RootRenderNode();
+    RootRenderNode* node = new RootRenderNode(env);
     node->incStrong(0);
     node->setName("RootRenderNode");
     return reinterpret_cast<jlong>(node);
@@ -238,11 +259,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,
@@ -252,10 +271,9 @@
 }
 
 static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jlong functorPtr, jboolean waitForCompletion) {
-    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+        jlong functorPtr, jboolean waitForCompletion) {
     Functor* functor = reinterpret_cast<Functor*>(functorPtr);
-    proxy->invokeFunctor(functor, waitForCompletion);
+    RenderProxy::invokeFunctor(functor, waitForCompletion);
 }
 
 static void android_view_ThreadedRenderer_runWithGlContext(JNIEnv* env, jobject clazz,
@@ -329,6 +347,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
 // ----------------------------------------------------------------------------
 
@@ -336,7 +366,7 @@
 
 static JNINativeMethod gMethods[] = {
 #ifdef USE_OPENGL_RENDERER
-    { "nSetAtlas", "(Landroid/view/GraphicBuffer;[J)V",   (void*) android_view_ThreadedRenderer_setAtlas },
+    { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V",   (void*) android_view_ThreadedRenderer_setAtlas },
     { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
     { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
@@ -347,9 +377,9 @@
     { "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 },
+    { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
     { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
     { "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer },
     { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer },
@@ -361,6 +391,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 381a5ee..226b764 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)
 
@@ -487,6 +490,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)
 {
@@ -518,6 +557,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/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c58bf04..0cdddba 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -14,46 +14,40 @@
  * limitations under the License.
  */
 
-#include "android_runtime/AndroidRuntime.h"
+#define LOG_TAG "Zygote"
 
 // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
 #include <sys/mount.h>
 #include <linux/fs.h>
 
 #include <grp.h>
+#include <fcntl.h>
 #include <paths.h>
 #include <signal.h>
 #include <stdlib.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
 #include <unistd.h>
-#include <fcntl.h>
+#include <sys/capability.h>
+#include <sys/personality.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
 
-#include "cutils/fs.h"
-#include "cutils/multiuser.h"
-#include "cutils/sched_policy.h"
-#include "utils/String8.h"
+
+#include <cutils/fs.h>
+#include <cutils/multiuser.h>
+#include <cutils/sched_policy.h>
+#include <utils/String8.h>
+#include <selinux/android.h>
+
+#include "android_runtime/AndroidRuntime.h"
 #include "JNIHelp.h"
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
 
-#if defined(HAVE_PRCTL)
-#include <sys/prctl.h>
-#endif
-
-#include <selinux/android.h>
-
-#if defined(__linux__)
-#include <sys/personality.h>
-#include <sys/utsname.h>
-#if defined(HAVE_ANDROID_OS)
-#include <sys/capability.h>
-#endif
-#endif
-
 namespace {
 
 using android::String8;
@@ -97,11 +91,9 @@
       if (WTERMSIG(status) != SIGKILL) {
         ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
       }
-#ifdef WCOREDUMP
       if (WCOREDUMP(status)) {
         ALOGI("Process %d dumped core.", pid);
       }
-#endif /* ifdef WCOREDUMP */
     }
 
     // If the just-crashed process is the system_server, bring down zygote
@@ -199,8 +191,6 @@
   }
 }
 
-#if defined(HAVE_ANDROID_OS)
-
 // The debug malloc library needs to know whether it's the zygote or a child.
 extern "C" int gMallocLeakZygoteChild;
 
@@ -254,17 +244,6 @@
   }
 }
 
-#else
-
-static int gMallocLeakZygoteChild = 0;
-
-static void EnableKeepCapabilities(JNIEnv*) {}
-static void DropCapabilitiesBoundingSet(JNIEnv*) {}
-static void SetCapabilities(JNIEnv*, int64_t, int64_t) {}
-static void SetSchedulerPolicy(JNIEnv*) {}
-
-#endif
-
 // Create a private mount namespace and bind mount appropriate emulated
 // storage for the given user.
 static bool MountEmulatedStorage(uid_t uid, jint mount_mode) {
@@ -337,7 +316,6 @@
   return true;
 }
 
-#if defined(__linux__)
 static bool NeedsNoRandomizeWorkaround() {
 #if !defined(__arm__)
     return false;
@@ -357,7 +335,6 @@
     return (major < 3) || ((major == 3) && (minor < 4));
 #endif
 }
-#endif
 
 // Utility to close down the Zygote socket file descriptors while
 // the child is still running as root with Zygote's privileges.  Each
@@ -474,7 +451,6 @@
       RuntimeAbort(env);
     }
 
-#if defined(__linux__)
     if (NeedsNoRandomizeWorkaround()) {
         // Work around ARM kernel ASLR lossage (http://b/5817320).
         int old_personality = personality(0xffffffff);
@@ -483,58 +459,49 @@
             ALOGW("personality(%d) failed", new_personality);
         }
     }
-#endif
 
     SetCapabilities(env, permittedCapabilities, effectiveCapabilities);
 
     SetSchedulerPolicy(env);
 
-#if defined(HAVE_ANDROID_OS)
-    {  // NOLINT(whitespace/braces)
-      const char* se_info_c_str = NULL;
-      ScopedUtfChars* se_info = NULL;
-      if (java_se_info != NULL) {
-          se_info = new ScopedUtfChars(env, java_se_info);
-          se_info_c_str = se_info->c_str();
-          if (se_info_c_str == NULL) {
-            ALOGE("se_info_c_str == NULL");
-            RuntimeAbort(env);
-          }
-      }
-      const char* se_name_c_str = NULL;
-      ScopedUtfChars* se_name = NULL;
-      if (java_se_name != NULL) {
-          se_name = new ScopedUtfChars(env, java_se_name);
-          se_name_c_str = se_name->c_str();
-          if (se_name_c_str == NULL) {
-            ALOGE("se_name_c_str == NULL");
-            RuntimeAbort(env);
-          }
-      }
-      rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
-      if (rc == -1) {
-        ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
-              is_system_server, se_info_c_str, se_name_c_str);
-        RuntimeAbort(env);
-      }
-
-      // Make it easier to debug audit logs by setting the main thread's name to the
-      // nice name rather than "app_process".
-      if (se_info_c_str == NULL && is_system_server) {
-        se_name_c_str = "system_server";
-      }
-      if (se_info_c_str != NULL) {
-        SetThreadName(se_name_c_str);
-      }
-
-      delete se_info;
-      delete se_name;
+    const char* se_info_c_str = NULL;
+    ScopedUtfChars* se_info = NULL;
+    if (java_se_info != NULL) {
+        se_info = new ScopedUtfChars(env, java_se_info);
+        se_info_c_str = se_info->c_str();
+        if (se_info_c_str == NULL) {
+          ALOGE("se_info_c_str == NULL");
+          RuntimeAbort(env);
+        }
     }
-#else
-    UNUSED(is_system_server);
-    UNUSED(java_se_info);
-    UNUSED(java_se_name);
-#endif
+    const char* se_name_c_str = NULL;
+    ScopedUtfChars* se_name = NULL;
+    if (java_se_name != NULL) {
+        se_name = new ScopedUtfChars(env, java_se_name);
+        se_name_c_str = se_name->c_str();
+        if (se_name_c_str == NULL) {
+          ALOGE("se_name_c_str == NULL");
+          RuntimeAbort(env);
+        }
+    }
+    rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
+    if (rc == -1) {
+      ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
+            is_system_server, se_info_c_str, se_name_c_str);
+      RuntimeAbort(env);
+    }
+
+    // Make it easier to debug audit logs by setting the main thread's name to the
+    // nice name rather than "app_process".
+    if (se_info_c_str == NULL && is_system_server) {
+      se_name_c_str = "system_server";
+    }
+    if (se_info_c_str != NULL) {
+      SetThreadName(se_name_c_str);
+    }
+
+    delete se_info;
+    delete se_name;
 
     UnsetSigChldHandler();
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c5fd83d..7db478d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -123,10 +123,30 @@
     <protected-broadcast
         android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
     <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.AG_EVENT" />
+    <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.RESULT" />
+    <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
+   <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
@@ -136,16 +156,12 @@
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED" />
     <protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" />
-    <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_HANDOVER_STARTED" />
-    <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />
     <protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" />
-    <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS" />
     <protected-broadcast android:name="android.btopp.intent.action.LIST" />
     <protected-broadcast android:name="android.btopp.intent.action.OPEN_OUTBOUND" />
     <protected-broadcast android:name="android.btopp.intent.action.HIDE_COMPLETE" />
     <protected-broadcast android:name="android.btopp.intent.action.CONFIRM" />
     <protected-broadcast android:name="android.btopp.intent.action.HIDE" />
-    <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_TRANSFER_DONE" />
     <protected-broadcast android:name="android.btopp.intent.action.RETRY" />
     <protected-broadcast android:name="android.btopp.intent.action.OPEN" />
     <protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />
@@ -260,6 +276,18 @@
     <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" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_STARTED" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
@@ -624,13 +652,27 @@
         android:permissionGroupFlags="personalInfo"
         android:priority="280" />
 
-   <!-- Allows an application to add voicemails into the system. -->
+    <!-- Allows an application to add voicemails into the system. -->
     <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
         android:permissionGroup="android.permission-group.VOICEMAIL"
         android:protectionLevel="dangerous"
         android:label="@string/permlab_addVoicemail"
         android:description="@string/permdesc_addVoicemail" />
 
+    <!-- Allows an application to remove any voicemails from the system. -->
+    <permission android:name="com.android.voicemail.permission.REMOVE_VOICEMAIL"
+        android:permissionGroup="android.permission-group.VOICEMAIL"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_removeVoicemail"
+        android:description="@string/permdesc_removeVoicemail" />
+
+    <!-- Allows an application to read all the voicemails in the system. -->
+    <permission android:name="com.android.voicemail.permission.READ_ALL_VOICEMAIL"
+        android:permissionGroup="android.permission-group.VOICEMAIL"
+        android:protectionLevel="dangerous"
+        android:label="@string/permlab_readAllVoicemail"
+        android:description="@string/permdesc_readAllVoicemail" />
+
     <!-- =============================================== -->
     <!-- Permissions for enabling accessibility features -->
     <!-- =============================================== -->
@@ -837,6 +879,14 @@
 	android:permissionGroup="android.permission-group.NETWORK"
 	android:protectionLevel="signature|system" />
 
+    <!-- Allows sending and receiving handover transfer status from Wifi and Bluetooth
+         @hide
+    -->
+    <permission android:name="android.permission.NFC_HANDOVER_STATUS"
+                android:label="@string/permlab_handoverStatus"
+                android:description="@string/permdesc_handoverStatus"
+                android:protectionLevel="signature|system" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
@@ -1007,6 +1057,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 +1804,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 +2115,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 -->
@@ -2623,10 +2687,16 @@
                 android:label="@string/permlab_provide_trust_agent"
                 android:description="@string/permdesc_provide_trust_agent" />
 
+    <!-- Allows an application to launch the trust agent settings activity.
+        @hide -->
+    <permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_launch_trust_agent_settings"
+        android:description="@string/permdesc_launch_trust_agent_settings" />
+
     <!-- 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"
@@ -2649,6 +2719,13 @@
         android:description="@string/permdesc_bindConditionProviderService"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an {@link android.service.dreams.DreamService},
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_DREAM_SERVICE"
+        android:label="@string/permlab_bindDreamService"
+        android:description="@string/permdesc_bindDreamService"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to call into a carrier setup flow. It is up to the
          carrier setup application to enforce that this permission is required
          @hide This is not a third-party API (intended for OEMs and system apps). -->
@@ -2689,7 +2766,8 @@
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true"
                 android:multiprocess="true"
-                android:documentLaunchMode="never">
+                android:documentLaunchMode="never"
+                android:relinquishTaskIdentity="true">
             <intent-filter>
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -2795,7 +2873,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">
@@ -2882,6 +2960,12 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service
+            android:name="com.android.server.pm.BackgroundDexOptService"
+            android:exported="true"
+            android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
     </application>
 
 </manifest>
diff --git a/core/res/assets/images/android-logo-mask.png b/core/res/assets/images/android-logo-mask.png
index 1e8ab95..ad40645 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..cb65f22 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/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 7e212be..ace17ae 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,7 +18,7 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
+    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100">
     <alpha
         android:fromAlpha="0.0" android:toAlpha="1.0"
         android:fillEnabled="true" android:fillBefore="true"
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/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
index 17acfc5..c5c0e97 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png
index 17acfc5..c5c0e97 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png
index 9b8ca22..3b31225 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png
index 9b8ca22..3b31225 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png
index bc20f6c..b65009e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png
index bc20f6c..b65009e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png
index 571819b..a2dfcae 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png
index 571819b..c3fda0e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
index 1f83b5a..bae60a7 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png
index 1f83b5a..bae60a7 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png
index 733cf45..a9653b0 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png
index 733cf45..a9653b0 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png
index 2265de4..394cb5e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png
index 2265de4..394cb5e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png
index f3ada58..aa23c6e 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png
index f3ada58..028b3b8 100644
--- a/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..4d97045
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..46fb463
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_recent.png b/core/res/res/drawable-hdpi/ic_recent.png
new file mode 100644
index 0000000..8866539
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_dark_blue.9.png b/core/res/res/drawable-hdpi/sim_dark_blue.9.png
new file mode 100755
index 0000000..b991535
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_dark_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_dark_green.9.png b/core/res/res/drawable-hdpi/sim_dark_green.9.png
new file mode 100755
index 0000000..c8de61d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_dark_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_dark_orange.9.png b/core/res/res/drawable-hdpi/sim_dark_orange.9.png
new file mode 100755
index 0000000..10347e8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_dark_orange.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_dark_purple.9.png b/core/res/res/drawable-hdpi/sim_dark_purple.9.png
new file mode 100755
index 0000000..ac4ee01
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_dark_purple.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_light_blue.9.png b/core/res/res/drawable-hdpi/sim_light_blue.9.png
new file mode 100755
index 0000000..b2c5581
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_light_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_light_green.9.png b/core/res/res/drawable-hdpi/sim_light_green.9.png
new file mode 100755
index 0000000..4d29c81
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_light_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_light_orange.9.png b/core/res/res/drawable-hdpi/sim_light_orange.9.png
new file mode 100755
index 0000000..68c6c2f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_light_orange.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sim_light_purple.9.png b/core/res/res/drawable-hdpi/sim_light_purple.9.png
new file mode 100755
index 0000000..4deb8dc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sim_light_purple.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/work_icon.png b/core/res/res/drawable-hdpi/work_icon.png
deleted file mode 100644
index be8a36e..0000000
--- a/core/res/res/drawable-hdpi/work_icon.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
index 3fdd3bc..4e6d076 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png
index 3fdd3bc..4e6d076 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png
index eaa02b3..ca61cb2 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png
index eaa02b3..ca61cb2 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png
index 28c8b94..b5999be 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png
index 28c8b94..b5999be 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png
index 6090cce..8392ac3 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png
index 6090cce..522bafd 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
index 3f2e982..ebb2f8b 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png
index 3f2e982..ebb2f8b 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png
index 14b958b..3fa20ca 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png
index 14b958b..3fa20ca 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png
index 4db22d4..6cc59ed 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png
index 4db22d4..6cc59ed 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png
index a11e1c7..a1fcd08 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png
index a11e1c7..c6c0224 100644
--- a/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..163f4a0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..bbfb83c7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_recent.png b/core/res/res/drawable-mdpi/ic_recent.png
new file mode 100644
index 0000000..2b607df
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_dark_blue.9.png b/core/res/res/drawable-mdpi/sim_dark_blue.9.png
new file mode 100755
index 0000000..d646a7f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_dark_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_dark_green.9.png b/core/res/res/drawable-mdpi/sim_dark_green.9.png
new file mode 100755
index 0000000..ee4ea0d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_dark_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_dark_orange.9.png b/core/res/res/drawable-mdpi/sim_dark_orange.9.png
new file mode 100755
index 0000000..b394999
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_dark_orange.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_dark_purple.9.png b/core/res/res/drawable-mdpi/sim_dark_purple.9.png
new file mode 100755
index 0000000..459b5d69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_dark_purple.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_light_blue.9.png b/core/res/res/drawable-mdpi/sim_light_blue.9.png
new file mode 100755
index 0000000..396ad70
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_light_blue.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_light_green.9.png b/core/res/res/drawable-mdpi/sim_light_green.9.png
new file mode 100755
index 0000000..a063174
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_light_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_light_orange.9.png b/core/res/res/drawable-mdpi/sim_light_orange.9.png
new file mode 100755
index 0000000..95ea88e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_light_orange.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sim_light_purple.9.png b/core/res/res/drawable-mdpi/sim_light_purple.9.png
new file mode 100755
index 0000000..b1bd35f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sim_light_purple.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
index c08deab..1e45530 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png
index c08deab..1e45530 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png
index 8b1a55c..2c63c5d 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png
index 8b1a55c..2c63c5d 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png
index 77cd1fa..dd5e26e 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png
index 77cd1fa..dd5e26e 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png
index e0e3540..aa9b3c5 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png
index e0e3540..367c25a 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_off_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
index 324e490..df28ad0 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png
index 324e490..df28ad0 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png
index e126cc6..3a27831 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png
index e126cc6..3a27831 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png
index 4c1f1b9..d68bdf4 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png
index 4c1f1b9..d68bdf4 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_focused_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png
index 219d37b..da03ec9 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png
index 219d37b..482b249 100644
--- a/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/btn_toggle_on_normal_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..21d4d53
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..2aeb9a2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_recent.png b/core/res/res/drawable-xhdpi/ic_recent.png
new file mode 100644
index 0000000..86316db
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..1b11b59
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..ae0d655
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_recent.png b/core/res/res/drawable-xxhdpi/ic_recent.png
new file mode 100644
index 0000000..e6bd125
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_recent.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
new file mode 100644
index 0000000..8774412
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_open_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
new file mode 100644
index 0000000..1375acc
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_lock_outline_wht_24dp.png
Binary files differ
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
index 5325712..16c101b 100644
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ b/core/res/res/drawable/ic_corp_badge.xml
@@ -15,20 +15,29 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
     <size
-        android:width="19.0dp"
-        android:height="19.0dp"/>
+        android:width="20.0dp"
+        android:height="20.0dp"/>
 
     <viewport
-        android:viewportWidth="19.0"
-        android:viewportHeight="19.0"/>
+        android:viewportWidth="20.0"
+        android:viewportHeight="20.0"/>
 
     <path
-        android:pathData="M9.5,9.5m-9.5,0.0a9.5,9.5 0.0,1.0 1.0,19.0 0.0a9.5,9.5 0.0,1.0 1.0,-19.0 0.0"
+        android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0"
         android:fill="#FF5722"/>
     <path
-        android:pathData="M12.667,7.125l-1.583,0.0L11.084,6.333l-0.792,-0.792L8.708,5.5410004L7.917,6.333l0.0,0.792L6.333,7.125c-0.438,0.0 -0.788,0.354 -0.788,0.792l-0.004,4.354c0.0,0.438 0.354,0.792 0.792,0.792l6.333,0.0c0.438,0.0 0.792,-0.354 0.792,-0.792L13.458,7.917C13.458,7.479 13.104,7.125 12.667,7.125zM10.094,10.687L8.906,10.687L8.906,9.5l1.188,0.0L10.094,10.687zM10.292,7.125L8.708,7.125L8.708,6.333l1.583,0.0L10.291,7.125z"
+        android:pathData="M11.139,12.149l-0.001,0.0L8.996,12.149l0.0,-0.571L4.738,11.578l-0.002,2.198c0.0,0.589 0.477,1.066 1.066,1.066l8.535,0.0c0.589,0.0 1.066,-0.477 1.066,-1.066l0.0,-2.198l-4.264,0.0L11.139,12.149z"
         android:fill="#FFFFFF"/>
     <path
-        android:pathData="M4.75,4.75 h9.5 v9.5 h-9.5z"
+        android:pathData="M8.996,10.006l2.143,0.0l0.0,0.52l4.442,0.0L15.580999,7.909c0.0,-0.589 -0.477,-1.066 -1.066,-1.066l-1.877,0.0L7.544,6.843L5.606,6.843c-0.589,0.0 -1.061,0.477 -1.061,1.066l-0.003,2.617l4.453,0.0L8.996,10.006L8.996,10.006z"
+        android:fill="#FFFFFF"/>
+    <path
+        android:pathData="M3.367,3.456 h13.016 v13.016 h-13.016z"
+        android:fill="#00000000"/>
+    <path
+        android:pathData="M7.368,5.263l5.263,0.0l0.0,1.053l-5.263,0.0z"
+        android:fill="#FFFFFF"/>
+    <path
+        android:pathData="M8.996,12.149l2.1419992,0.0 0.0010004044,0.0 0.0,-0.5699997 -2.1429996,0.0z"
         android:fill="#00000000"/>
 </vector>
diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge.xml
index 7bfab4c..c8e49e1 100644
--- a/core/res/res/drawable/ic_corp_icon_badge.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge.xml
@@ -24,17 +24,31 @@
 
     <path
         android:fill="#FF000000"
-        android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/>
+        android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
+        android:fillOpacity="0.2"/>
     <path
         android:fill="#FF000000"
-        android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/>
+        android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
+        android:fillOpacity="0.2"/>
     <path
         android:pathData="M49.0,49.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
         android:fill="#FF5722"/>
     <path
-        android:pathData="M53.667,45.5l-2.333,0.0l0.0,-1.167l-1.167,-1.167l-2.333,0.0l-1.167,1.167L46.667,45.5l-2.333,0.0c-0.645,0.0 -1.161,0.522 -1.161,1.167l-0.006,6.417c0.0,0.645 0.522,1.167 1.167,1.167l9.333,0.0c0.645,0.0 1.167,-0.522 1.167,-1.167l0.0,-6.417C54.833,46.022 54.311,45.5 53.667,45.5zM49.875,50.75l-1.75,0.0L48.125,49.0l1.75,0.0L49.875,50.75zM50.167,45.5l-2.333,0.0l0.0,-1.167l2.333,0.0L50.167,45.5z"
+        android:pathData="M50.594,52.009l-3.0,0.0L47.594,51.0l-5.961,0.0l-0.003,3.289c0.0,0.826 0.668,1.494 1.494,1.494l11.948,0.0c0.826,0.0 1.494,-0.668 1.494,-1.494L56.566006,51.0l-5.972,0.0C50.594,51.0 50.594,52.009 50.594,52.009z"
         android:fill="#FFFFFF"/>
     <path
-        android:pathData="M42.0,42.0 h14.0 v14.0 h-14.0z"
+        android:pathData="M47.594,49.009l3.0,0.0L50.594,50.0l6.22,0.0l0.0,-3.925c0.0,-0.826 -0.668,-1.494 -1.494,-1.494l-2.627,0.0l-7.131,-0.001l-2.713,0.0c-0.826,0.0 -1.486,0.668 -1.486,1.494L41.359,50.0l6.235,0.0L47.594,49.009z"
+        android:fill="#FFFFFF"/>
+    <path
+        android:pathData="M39.714,39.838 h18.221 v18.221 h-18.221z"
         android:fill="#00000000"/>
+    <path
+        android:pathData="M47.594,49.009 h3.0 v0.991 h-3.0z"
+        android:fill="#00000000"/>
+    <path
+        android:pathData="M47.594,51.0 h3.0 v1.009 h-3.0z"
+        android:fill="#00000000"/>
+    <path
+        android:pathData="M46.0,43.0l6.0,0.0l0.0,1.0l-6.0,0.0z"
+        android:fill="#FFFFFF"/>
 </vector>
diff --git a/core/res/res/drawable/lock_task_notify_bg.xml b/core/res/res/drawable/lock_task_notify_bg.xml
new file mode 100644
index 0000000..3a8fab5
--- /dev/null
+++ b/core/res/res/drawable/lock_task_notify_bg.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+
+    <solid android:color="@android:color/black" />
+    <corners
+        android:radius="8dip" />
+</shape>
diff --git a/core/res/res/drawable/view_accessibility_focused.xml b/core/res/res/drawable/view_accessibility_focused.xml
index 0da9a81..68e3f1e 100644
--- a/core/res/res/drawable/view_accessibility_focused.xml
+++ b/core/res/res/drawable/view_accessibility_focused.xml
@@ -17,9 +17,11 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <stroke
-        android:width="2dp"
-        android:color="@color/accessibility_focus_highlight" />
+        android:width="4dp"
+        android:color="@color/accessibility_focus_highlight"
+        android:dashWidth="4dp"
+        android:dashGap="2dp" />
 
-    <corners android:radius="2dp"/>
+    <corners android:radius="2dp" />
 
 </shape>
diff --git a/core/res/res/layout/alert_dialog_leanback.xml b/core/res/res/layout/alert_dialog_leanback.xml
index 8655aea..848015c 100644
--- a/core/res/res/layout/alert_dialog_leanback.xml
+++ b/core/res/res/layout/alert_dialog_leanback.xml
@@ -1,20 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** 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.
-*/
+     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
@@ -22,11 +20,13 @@
     android:id="@+id/parentPanel"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:background="@drawable/dialog_background_material"
+    android:translationZ="@dimen/floating_window_z"
     android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
     android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin"
-    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin"
-    android:orientation="vertical">
+    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
+    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
@@ -37,16 +37,17 @@
             android:layout_height="wrap_content"
             android:orientation="horizontal"
             android:gravity="center_vertical|start"
-            android:minHeight="@dimen/alert_dialog_title_height"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip">
+            android:paddingStart="16dip"
+            android:paddingEnd="16dip"
+            android:paddingTop="16dip">
             <ImageView android:id="@+id/icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingEnd="8dip"
+                android:layout_width="32dip"
+                android:layout_height="32dip"
+                android:layout_marginEnd="8dip"
+                android:scaleType="fitCenter"
                 android:src="@null" />
-            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
-                style="?android:attr/windowTitleStyle"
+            <TextView android:id="@+id/alertTitle"
+                style="?attr/windowTitleStyle"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:layout_width="match_parent"
@@ -67,13 +68,12 @@
             android:layout_height="wrap_content"
             android:clipToPadding="false">
             <TextView android:id="@+id/message"
-                style="?android:attr/textAppearanceMedium"
+                style="?attr/textAppearanceMedium"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingStart="16dip"
                 android:paddingEnd="16dip"
-                android:paddingTop="8dip"
-                android:paddingBottom="8dip"/>
+                android:paddingTop="16dip" />
         </ScrollView>
     </LinearLayout>
 
@@ -91,41 +91,32 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="@dimen/alert_dialog_button_bar_height"
-        android:orientation="vertical">
+        android:orientation="vertical"
+        android:gravity="end"
+        android:padding="16dip">
         <LinearLayout
-            style="?android:attr/buttonBarStyle"
-            android:layout_width="match_parent"
+            style="?attr/buttonBarStyle"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layoutDirection="locale"
-            android:measureWithLargestChild="true">
-            <Button android:id="@+id/button2"
-                android:layout_width="wrap_content"
-                android:layout_gravity="start"
-                android:layout_weight="1"
-                android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
+            android:layoutDirection="locale">
             <Button android:id="@+id/button3"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
-                android:layout_gravity="center_horizontal"
-                android:layout_weight="1"
+                android:layout_height="wrap_content"
                 android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button2"
+                style="?attr/buttonBarButtonStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button1"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
-                android:layout_gravity="end"
-                android:layout_weight="1"
+                android:layout_height="wrap_content"
                 android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:layout_height="wrap_content" />
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
         </LinearLayout>
      </LinearLayout>
 </LinearLayout>
diff --git a/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml b/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml
index 096b015..829d5aa 100644
--- a/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml
+++ b/core/res/res/layout/alert_dialog_leanback_button_panel_right.xml
@@ -1,20 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** 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.
-*/
+     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
@@ -22,18 +20,20 @@
     android:id="@+id/parentPanel"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:background="@drawable/dialog_background_material"
+    android:translationZ="@dimen/floating_window_z"
     android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
     android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin"
-    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin"
-    android:orientation="horizontal">
+    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
+    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin">
 
-  <LinearLayout
-      android:id="@+id/leftPanel"
-      android:layout_width="0dp"
-      android:layout_weight="0.66"
-      android:layout_height="wrap_content"
-      android:orientation="vertical">
+   <LinearLayout
+       android:id="@+id/leftPanel"
+       android:layout_width="0dp"
+       android:layout_weight="1"
+       android:layout_height="wrap_content"
+       android:orientation="vertical">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
@@ -44,16 +44,17 @@
             android:layout_height="wrap_content"
             android:orientation="horizontal"
             android:gravity="center_vertical|start"
-            android:minHeight="@dimen/alert_dialog_title_height"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip">
+            android:paddingStart="16dip"
+            android:paddingEnd="16dip"
+            android:paddingTop="16dip">
             <ImageView android:id="@+id/icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingEnd="8dip"
+                android:layout_width="32dip"
+                android:layout_height="32dip"
+                android:layout_marginEnd="8dip"
+                android:scaleType="fitCenter"
                 android:src="@null" />
-            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
-                style="?android:attr/windowTitleStyle"
+            <TextView android:id="@+id/alertTitle"
+                style="?attr/windowTitleStyle"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:layout_width="match_parent"
@@ -66,6 +67,7 @@
     <LinearLayout android:id="@+id/contentPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_weight="1"
         android:orientation="vertical"
         android:minHeight="64dp">
         <ScrollView android:id="@+id/scrollView"
@@ -73,13 +75,13 @@
             android:layout_height="wrap_content"
             android:clipToPadding="false">
             <TextView android:id="@+id/message"
-                style="?android:attr/textAppearanceMedium"
+                style="?attr/textAppearanceMedium"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingStart="16dip"
                 android:paddingEnd="16dip"
-                android:paddingTop="8dip"
-                android:paddingBottom="8dip"/>
+                android:paddingTop="16dip"
+		android:paddingBottom="16dip" />
         </ScrollView>
     </LinearLayout>
 
@@ -92,51 +94,41 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
     </FrameLayout>
-  </LinearLayout>
+    </LinearLayout>
 
     <LinearLayout android:id="@+id/buttonPanel"
-        android:layout_width="0dp"
-	android:layout_weight="0.33"
-        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+android:background="#ffffff"
+        android:layout_height="wrap_content"
+	android:layout_gravity="center_vertical"
         android:minHeight="@dimen/alert_dialog_button_bar_height"
-	android:paddingLeft="32dp"
-	android:paddingRight="32dp"
-        android:orientation="horizontal">
+        android:orientation="vertical"
+        android:gravity="end"
+        android:padding="16dip">
         <LinearLayout
-            style="?android:attr/buttonBarStyle"
-            android:layout_width="match_parent"
+            style="?attr/buttonBarStyle"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-	    android:layout_gravity="center_vertical"
-            android:orientation="vertical"
             android:layoutDirection="locale"
-            android:measureWithLargestChild="true">
-            <Button android:id="@+id/button1"
-                android:layout_width="match_parent"
-                android:gravity="center_vertical"
-                android:layout_weight="1"
-                android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
+	    android:orientation="vertical">
             <Button android:id="@+id/button3"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="match_parent"
-                android:gravity="center_vertical"
-                android:layout_weight="1"
+                android:layout_height="wrap_content"
                 android:maxLines="2"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content" />
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button2"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="match_parent"
-                android:gravity="center_vertical"
-                android:layout_weight="1"
+                android:layout_height="wrap_content"
                 android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?android:attr/buttonBarButtonStyle"
-                android:textSize="14sp"
-                android:layout_height="wrap_content" />
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button1"
+                style="?attr/buttonBarButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
         </LinearLayout>
      </LinearLayout>
 </LinearLayout>
diff --git a/core/res/res/layout/lock_to_app_enter.xml b/core/res/res/layout/lock_to_app_enter.xml
new file mode 100644
index 0000000..c034536
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_enter.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="20dp"
+    android:paddingRight="20dp"
+    android:paddingBottom="12dp"
+    android:alpha=".8"
+    android:background="@drawable/lock_task_notify_bg" >
+
+    <ImageView
+        android:id="@+id/lock_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="20dp"
+        android:alpha=".8"
+        android:src="@drawable/ic_lock_outline_wht_24dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/lock_icon"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="2dp"
+        android:textColor="@android:color/white"
+        android:alpha=".8"
+        android:text="@string/lock_to_app_start" />
+</RelativeLayout>
diff --git a/core/res/res/layout/lock_to_app_exit.xml b/core/res/res/layout/lock_to_app_exit.xml
new file mode 100644
index 0000000..4a60c80
--- /dev/null
+++ b/core/res/res/layout/lock_to_app_exit.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="20dp"
+    android:paddingRight="20dp"
+    android:paddingBottom="12dp"
+    android:alpha=".8"
+    android:background="@drawable/lock_task_notify_bg" >
+
+    <ImageView
+        android:id="@+id/lock_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="20dp"
+        android:alpha=".8"
+        android:src="@drawable/ic_lock_open_wht_24dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/lock_icon"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="2dp"
+        android:textColor="@android:color/white"
+        android:alpha=".8"
+        android:text="@string/lock_to_app_exit" />
+</RelativeLayout>
diff --git a/core/res/res/layout/preference_widget_seekbar.xml b/core/res/res/layout/preference_widget_seekbar.xml
index c427965..05daa1a 100644
--- a/core/res/res/layout/preference_widget_seekbar.xml
+++ b/core/res/res/layout/preference_widget_seekbar.xml
@@ -14,9 +14,7 @@
      limitations under the License.
 -->
 
-<!-- Layout for a Preference in a PreferenceActivity. The
-     Preference is able to place a specific widget for its particular
-     type in the "widget_frame" layout. -->
+<!-- Layout used by SeekBarPreference for the seekbar widget style. -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/core/res/res/layout/preference_widget_seekbar_material.xml b/core/res/res/layout/preference_widget_seekbar_material.xml
new file mode 100644
index 0000000..f70a472
--- /dev/null
+++ b/core/res/res/layout/preference_widget_seekbar_material.xml
@@ -0,0 +1,86 @@
+<?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.
+-->
+
+<!-- Layout used by SeekBarPreference for the seekbar widget style. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="?attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?attr/listPreferredItemPaddingEnd">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:minWidth="@dimen/preference_icon_minWidth"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:minWidth="48dp"
+            />
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dip"
+        android:layout_marginEnd="8dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="4" />
+
+        <!-- Preference should place its actual preference widget here. -->
+        <LinearLayout android:id="@+android:id/widget_frame"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_below="@android:id/summary"
+            android:layout_alignStart="@android:id/title"
+            android:minWidth="@dimen/preference_widget_width"
+            android:gravity="center"
+            android:orientation="vertical" />
+
+        <SeekBar android:id="@+android:id/seekbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/summary"
+            android:layout_toEndOf="@android:id/widget_frame"
+            android:layout_alignParentEnd="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/progress_dialog_leanback.xml b/core/res/res/layout/progress_dialog_leanback.xml
deleted file mode 100644
index 6bcad7a..0000000
--- a/core/res/res/layout/progress_dialog_leanback.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** 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.
-*/
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="2dip"
-        android:background="@android:color/leanback_dark_gray" />
-    <LinearLayout android:id="@+id/body"
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:baselineAligned="false"
-        android:padding="16dip">
-
-        <ProgressBar android:id="@android:id/progress"
-            style="?android:attr/progressBarStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:max="10000"
-            android:layout_marginEnd="16dip" />
-
-        <TextView android:id="@+id/message"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index 8bf8416..5acb588 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -34,7 +34,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
-        android:viewName="android:action_bar"
+        android:transitionName="android:action_bar"
         android:gravity="top">
         <com.android.internal.widget.ActionBarView
             android:id="@+id/action_bar"
diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml
index c8952bf..b385bed 100644
--- a/core/res/res/layout/screen_custom_title.xml
+++ b/core/res/res/layout/screen_custom_title.xml
@@ -31,7 +31,7 @@
     <FrameLayout android:id="@android:id/title_container" 
         android:layout_width="match_parent" 
         android:layout_height="?android:attr/windowTitleSize"
-        android:viewName="android:title"
+        android:transitionName="android:title"
         style="?android:attr/windowTitleBackgroundStyle">
     </FrameLayout>
     <FrameLayout android:id="@android:id/content"
diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml
index 290c7da..56815f8 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -34,7 +34,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
-        android:viewName="android:action_bar"
+        android:transitionName="android:action_bar"
         android:gravity="top">
         <Toolbar
             android:id="@+id/action_bar"
diff --git a/core/res/res/layout/subscription_item_layout.xml b/core/res/res/layout/subscription_item_layout.xml
new file mode 100755
index 0000000..9f8f2b3
--- /dev/null
+++ b/core/res/res/layout/subscription_item_layout.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright (C) 2014 MediaTek Inc.
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" >
+    <RelativeLayout
+        android:layout_width="48dip"
+        android:layout_height="32dip"
+        android:id="@+id/sub_color"
+        android:layout_marginEnd="6dip"
+        android:layout_centerVertical="true">
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/sub_short_number"
+            android:layout_marginBottom="2dip"
+            android:layout_marginEnd="4dip"
+            android:layout_alignParentEnd="true" 
+            android:layout_alignParentBottom="true"
+            android:textSize="12sp"
+            android:singleLine="true"
+            android:textColor="@android:color/white"
+            android:includeFontPadding="false"/>
+    </RelativeLayout>
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_centerVertical="true">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/sub_name"
+            android:singleLine="true"
+            android:ellipsize="none"
+            android:requiresFadingEdge="horizontal"
+            android:scrollHorizontally="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/sub_number"
+            android:layout_below="@+id/sub_name"
+            android:layout_alignStart="@+id/sub_name"
+            android:singleLine="true"
+            android:ellipsize="none"
+            android:requiresFadingEdge="horizontal"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"/>
+    </RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/transition/explode.xml b/core/res/res/transition/explode.xml
new file mode 100644
index 0000000..fe22284
--- /dev/null
+++ b/core/res/res/transition/explode.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<explode xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/core/res/res/transition/move.xml b/core/res/res/transition/move.xml
index d4863ee..56e1938 100644
--- a/core/res/res/transition/move.xml
+++ b/core/res/res/transition/move.xml
@@ -14,17 +14,8 @@
      limitations under the License.
 -->
 <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
-    <transitionSet>
-        <changeBounds/>
-        <changeTransform/>
-        <changeClipBounds/>
-        <targets>
-            <target android:excludeClass="android.widget.ImageView"/>
-        </targets>
-    </transitionSet>
-    <moveImage>
-        <targets>
-            <target android:targetClass="android.widget.ImageView"/>
-        </targets>
-    </moveImage>
+    <changeBounds/>
+    <changeTransform/>
+    <changeClipBounds/>
+    <changeImageTransform/>
 </transitionSet>
diff --git a/core/res/res/transition/slide_bottom.xml b/core/res/res/transition/slide_bottom.xml
new file mode 100644
index 0000000..46dc0d6
--- /dev/null
+++ b/core/res/res/transition/slide_bottom.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="bottom"/>
diff --git a/core/res/res/transition/slide_left.xml b/core/res/res/transition/slide_left.xml
new file mode 100644
index 0000000..997bd97
--- /dev/null
+++ b/core/res/res/transition/slide_left.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="left"/>
diff --git a/core/res/res/transition/slide_right.xml b/core/res/res/transition/slide_right.xml
new file mode 100644
index 0000000..98f8f6a
--- /dev/null
+++ b/core/res/res/transition/slide_right.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="right"/>
diff --git a/core/res/res/transition/slide_top.xml b/core/res/res/transition/slide_top.xml
new file mode 100644
index 0000000..07ab945
--- /dev/null
+++ b/core/res/res/transition/slide_top.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="top"/>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 9e7a4d1..7cf4e78 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Vliegtuigmodus is AAN"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Vliegtuigmodus is AF"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Instellings"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Sluit nou"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Dit laat die houer toe om aan die topvlak-koppelvlak van \'n muurpapier te bind. Dit moet nooit vir normale programme nodig wees nie."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"verbind met \'n steminteraksiediens"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Laat die houer toe om met die topvlak-koppelvlak van \'n steminteraksiediens te verbind. Behoort nooit vir normale programme nodig te wees nie."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"bestuur stemsleutelfrases"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Laat die houer toe om die sleutelfrases vir aktiveerwoordbespeuring met stem te bestuur. Behoort nooit vir normale programme nodig te wees nie."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"koppel aan \'n afstandskerm"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Dit laat die houer toe om aan die top-koppelvlak van \'n afstandskerm te koppel. Behoort nooit vir gewone programme nodig te wees nie."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind aan \'n legstukdiens"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Laat die program toe om die kalibrasieparameters van die raakskerm te wysig. Dit behoort nooit vir normale programme nodig te wees nie."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"gaan in by DRM-sertifikate"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Laat \'n program toe om DRM-sertifikate op te stel en te gebruik. Behoort nooit vir normale programme nodig te wees nie."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Ontvang oordraguitsendings."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Laat toe dat oordragstatusinligting ontvang word."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Stel wagwoordreëls"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Beheer lengte en watter karakters wat in die skermontsluit-wagwoorde gebruik word."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitor pogings om skerm te ontsluit"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Laat die program toe om die blaaier se geskiedenis of boekmerke wat op jou foon gestoor is, te verander. Dit kan moontlik die program toelaat om blaaierdata uit te vee of te verander. Let wel: hierdie toestemming mag dalk nie deur derdeparty-blaaiers of ander programme met webblaaivermoëns afgedwing word nie."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"stel \'n wekker"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Laat die program toe om \'n alarm in \'n geïnstalleerde wekkerprogram te stel. Sommige wekkerprogramme werk dalk nie met hierdie funksie nie."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"verwyder stemposse"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Laat die program toe om boodskappe uit jou stemposinkassie te verwyder."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"voeg stemboodskap by"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Laat die program toe om boodskappe by te voeg by jou stempos-inkassie."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lees alle stempos"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Laat die program toe om al jou stemposse te lees."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"verander blaaier se geoligging-toestemmings"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Laat die program toe om die blaaier se geoligging-toestemmings te verander. Kwaadwillige programme kan dit gebruik om hulle toe te laat om ligginginligting aan enige webwerf te stuur."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifieer pakkies"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Laat \'n program toe om vir veranderinge in vertrouenstaat te luister."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Voorsien \'n vertroude agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Laat \'n program toe om \'n vertroude agent te voorsien."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Begin vertrouensagente se instellingskieslys."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Laat \'n program toe om \'n aktiwiteit te begin wat die vertrouensagent se gedrag verander."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Verbind met \'n vertrouensagentdiens"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Laat \'n program toe om met \'n vertrouensagentdiens te verbind."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Tree in wisselwerking met opdatering- en terugstellingstelsel"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Kies jaar"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> gekies"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> uitgevee"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk-<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Gebruik Sluit-na-program?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Sluit-na-program sluit die skerm in \'n enkele program.\n\nOm uit te gaan, druk en hou die knoppie vir onlangse programme $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"BEGIN"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Begin Sluit-na-program"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Gaan uit Sluit-na-program"</string>
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 14d0af2..14ad538 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"የአውሮፕላንሁነታ በርቷል"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"የአውሮፕላንሁነታ ጠፍቷል"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"ቅንብሮች"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"አሁን ቆልፍ"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ያዡ ግቤት ስልቱን ወደ ከፍተኛ-ደረጃ ልጣፍ ለመጠረዝ ይፈቅዳሉ። ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ከአንድ የድምጽ በይነተገናኝ ጋር ይሰሩ"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"ያዢው የአንድ የድምጽ በይነግንኙነት አገልግሎት የከፍተኛ ደረጃ በይነገጽ እንዲያስር ያስችለዋል። ለመደበኛ መተግበሪያዎች በጭራሽ አያስፈልግም።"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ድምጽ የቁልፍ ሃረጎችን አስተዳድር"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"መያዣው የቁልፍ ሃረጎችን ለድምጽ ትኩስ ቃል ለይቶ ማወቂያ እንዲያስተዳድር ይፈቅድለታል። ለመደበኛ መተግበሪያዎች ማስፈለግ የለበትም።"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ከአንድ የርቀት ማሳያ ጋር ይጠርዛል"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"ያዢው ከአንድ የርቀት ማሳያ ከፍተኛ-ደረጃ በይነገጽ ጋር እንዲጠርዝ ይፈቅድለታል። ለመደበኛ መተግበሪያዎች በጭራሽ አያስፈልግም።"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ወደ ፍርግም አገልግሎት አያይዝ"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"መተግበሪያው የማያ ንካ የማስተካከያ ልኬቶቹን እንዲቀይር ያስችለዋል። ለመደበኛ መተግበሪያዎች በጭራሽ ሊያስፈልግ አይገባም።"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"የDRM የምስክር ወረቀቶች ላይ ይድረሱ"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"አንድ መተግበሪያ የDRM የምስክር ወረቀቶችን እንዲሰጥና እንዲጠቀም ያስችላል። ለመደበኛ መተግበሪያዎች በፍጹም አስፈላጊ አይሆንም።"</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"የርክክብ ትልልፍ ስርጭቶችን ተቀበል።"</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"የርክክብ ትልልፍ ሁኔታ መረጃ እንዲቀበል ያስችላል።"</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"የይለፍ ቃል ደንቦች አዘጋጅ"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"በማያ-መክፈት የተፈቀዱ የይለፍ ቃል ርዝመት እና ቁምፊዎች ተቆጣጠር።"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"የማሳያ-ክፈት ሙከራዎችን አሳይ"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"መተግበሪያው ስልክህ ላይ የተከማቹ የአሳሹን ታሪክ ወይም ዕልባቶችን እንዲቀይር ይፈቅድለታል። ይህ መተግበሪያው የአሳሽ ውሂብ እንዲያጠፋ ወይም እንዲያስተካክል ሊፈቅድለት ይችላል። ማስታወሻ፦ ይህ ፈቃድ በሶስተኛ ወገን አሳሾች ወይም በሌላ የድር አሳሽነት አቅም ባላቸው መተግበሪያዎች ላይፈጸም ይችላል።"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ማንቂያ አስቀምጥ"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"በተጫነው የማንቂያ ሰዓት መተግበሪያ ውስጥ ማንቅያን ለማደራጀት ለመተግበሪያው ይፈቅዳሉ፡፡አንዳንድ የማንቂያ ሰዓት መተግበሪያዎች ይሄንን ባህሪ ላይፈፅሙ ይችላሉ፡፡"</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"የድምጽ መልዕክቶችን ያስወግዳል"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"መተግበሪያው መልዕክቶችን ከገቢ የድምጽ መልዕክት ሳጥዎ እንዲያስወግድ ያስችለዋል።"</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"የድምፅ መልዕክት አክል"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ወደ ድምፅ መልዕክት የገቢ መልዕክትህ መልዕክቶች ለማከል ለመተግበሪያው ይፈቅዳሉ።"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ሁሉንም የድምጽ መልዕክት ያነብባል"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"መተግበሪያው ሁሉንም የድምጽ መልዕክቶችዎ እንዲያነባቸው ያስችለዋል።"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"የአሳሽ ገፀ ሥፍራ ፍቃዶችን ቀይር"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"የአሳሹን የጂኦ-አካባቢ ፍቃዶችን እንዲለውጥ ለመተግበሪያው ይፈቅዳል፡፡ተንኮል አዘል መተግበሪያዎች የመላኪያ አከባቢን መረጃ ወደ አጠራጣሪ የድር ጣቢያዎች ለመፍቀድ ይሄንን ሊጠቀሙበት ይችላሉ፡፡"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ፓኬጆችን አረጋግጥ"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"መተግበሪያው በተአማኒነት ሁኔታ ውስጥ ለውጦችን እንዲያዳምጥ ይፈቅዳል።"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"የመታመን ወኪል ያቅርቡ።"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"አንድ መተግበሪያ የመታመን ወኪል እንዲያቀርብ ይፈቅድለታል።"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"የእምነት ወኪል ቅንብሮች ምናሌን አስጀምር።"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"አንድ መተግበሪያ የእምነት ወኪል ባህሪ የሚቀይር እንቅስቃሴ እንዲያስጀምር ያስችለዋል።"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ለተአማኒነት ወኪል አገልግሎት ተገዢ አድርግ"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"ለመተግበሪያን የተአማኒነት ወኪል አገልግሎትን እንዲያከብር ይፈቅዳል።"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"ከዝማኔዎች እና ከመልሶ ማግኛ ስርዓቶች ጋር ይገናኙ"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"ዓመት ይምረጡ"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ተመርጧል"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ተሰርዟል"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"ስራ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"መተግበሪያ-ላይ-ቆልፍን ይጠቀሙ?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"መተግበሪያ-ላይ-ቆልፍ ማሳያውን በአንዲት መተግበሪያ ውስጥ ይቆልፋል።\n\nለመውጣት የቅርብ ጊዜ መተግበሪያዎች አዝራር $ን ተጭነው ይያዙት"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"አይ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ጀምር"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"መተግበሪያ-ላይ-ቆልፍን ጀምር"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"ከመተግበሪያ-ላይ-ቆልፍ ውጣ"</string>
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 319d210..77a2fad 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"وضع الطائرة قيد التشغيل"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"وضع الطائرة متوقف"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"الإعدادات"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"قفل الآن"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"للسماح للمالك بالالتزام بواجهة المستوى العلوي للخلفية. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"الربط بخدمة التفاعل الصوتي"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"للسماح للمالك بالربط بواجهة المستوى العلوي لخدمة التفاعل الصوتي. لن تكون هناك حاجة إلى هذا الإعداد مطلقًا مع التطبيقات العادية."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"إدارة العبارات الأساسية الصوتية"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"للسماح للمالك بإدارة العبارات الأساسية لاكتشاف الكلمات المهمة الصوتية. لا يجب استخدامه على الإطلاق للتطبيقات العادية."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"الربط بالشاشة عن بُعد"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"للسماح للمالك بالالتزام بواجهة المستوى العلوي للعرض عن بُعد. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"الالتزام بخدمة أداة"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"يتيح للتطبيق إمكانية تعديل معلمات المعايرة في شاشة اللمس. يجب عدم اللجوء إليه مع التطبيقات العادية."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"‏الدخول إلى شهادات DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"‏للسماح لأحد التطبيقات بتقديم شهادات DRM واستخدامها. لا يجب أن يكون ذلك لازمًا مطلقًا مع التطبيقات العادية."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"استلام عمليات البث المتعلقة بنقل إمكانية التحكم."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"للسماح باستلام معلومات حالة نقل إمكانية التحكم."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"تعيين قواعد كلمة المرور"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"يمكنك التحكم في الطول والأحرف المسموح بها في كلمات مرور إلغاء تأمين الشاشة."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"مراقبة محاولات إلغاء قفل الشاشة"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"للسماح للتطبيق بتعديل سجل المتصفح أو الإشارات المرجعية المخزنة على هاتفك. وقد يتيح هذا للتطبيق محو بيانات المتصفح أو تعديلها. ملاحظة: لا يجوز فرض هذا الإذن من قِبل متصفحات جهات خارجية أو تطبيقات أخرى بها إمكانيات تصفح الويب."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"تعيين منبه"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"للسماح للتطبيق بضبط المنبه في تطبيق المنبه المثبّت. ربما لا تنفذ بعض تطبيقات المنبه هذه الميزة."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"إزالة رسائل البريد الصوتي"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"للسماح للتطبيق بإزالة الرسائل من صندوق البريد الصوتي."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"إضافة بريد صوتي"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"للسماح للتطبيق بإضافة رسائل إلى صندوق البريد الصوتي."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"قراءة جميع رسائل البريد الصوتي"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"للسماح للتطبيق بقراءة جميع رسائل البريد الصوتي."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"تعديل أذونات الموقع الجغرافي للمتصفح"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"للسماح لأحد التطبيقات بتعديل أذونات الموقع الجغرافي للمتصفح. يمكن أن تستخدم التطبيقات الضارة هذا للسماح بإرسال معلومات الموقع إلى مواقع ويب عشوائية."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"التحقق من الحزم"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"للسماح للتطبيق بالتعرف على التغييرات في حالة الاعتماد."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"توفير وكيل معتمد."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"للسماح لأحد التطبيقات بتوفير وكيل معتمد."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"تشغيل قائمة إعدادات الوكيل المعتمد."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"للسماح لأحد التطبيقات بتشغيل نشاط يؤدي إلى تغيير سلوك الوكيل المعتمد."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"الالتزام بخدمة الوكيل المعتمد"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"للسماح لأحد التطبيقات بالالتزام بخدمة الوكيل المعتمد."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"التفاعل مع نظام التحديث والاسترداد"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"تحديد العام"</string>
     <string name="item_is_selected" msgid="949687401682476608">"تم تحديد <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"تم حذف <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> المخصص للعمل"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"هل تريد استخدام قفل تشغيل الجهاز على تطبيق؟"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"تتيح ميزة \"قفل تشغيل الجهاز على تطبيق\" تأمين الشاشة وقصر استخدامها على تطبيق واحد.\n\nللخروج اضغط مع الاستمرار على زر التطبيقات الحديثة $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"لا"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"بدء"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"بدء قفل تشغيل الجهاز على تطبيق"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج من قفل تشغيل الجهاز على تطبيق"</string>
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 21545aa..bd518ad 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Самолетният режим е ВКЛЮЧЕН"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Самолетният режим е ИЗКЛЮЧЕН"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Настройки"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Разрешава на притежателя да се обвърже с интерфейса от най-високото ниво на тапет. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"свързване с услуга за гласово взаимодействие"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Разрешава на притежателя да се свърже с интерфейса от най-високото ниво на услуга за гласово взаимодействие. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"управление на гласовите ключови фрази"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Разрешава на притежателя да управлява ключовите фрази за функцията за откриване на произнесени активиращи думи. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"свързване с отдалечен екран"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Разрешава на притежателя да се свърже с интерфейса от първо ниво на отдалечен екран. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"обвързване с услуга за приспособления"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Разрешава на приложението да променя параметрите на калибриране на сензорния екран. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"достъп до сертификатите за управление на цифровите права (DRM)"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Разрешава на приложението да обезпечава и използва сертификатите за управление на цифровите права (DRM). Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Задаване на правила за паролата"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролирайте дължината и разрешените знаци за паролите за отключване на екрана."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Наблюдаване на опитите за отключване на екрана"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Разрешава на приложението да променя историята или отметките на браузъра, съхранени на телефона ви. Това може да му позволи да изтрива или променя данните на браузъра. Забележка: Възможно е браузъри на трети страни или други приложения с възможности за сърфиране в мрежата да не могат да наложат ограниченията на разрешението."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"навиване на будилника"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Разрешава на приложението да навие инсталирано приложение будилник. Някои будилници може да не изпълнят тази функция."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"добавяне на гласова поща"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Разрешава на приложението да добавя съобщения към входящата ви гласова поща."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"четене на цялата гласова поща"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Разрешава на приложението да чете цялата ви гласова поща."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"промяна на разрешенията за местоположение в браузъра"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Разрешава на приложението да променя разрешенията на браузъра за местоположение. Злонамерените приложения могат да използват това, за да изпращат информация за местоположението до произволни уебсайтове."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"проверка на пакетите"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Разрешава на приложението да следи за промени в състоянието на надеждност."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Предоставяне на trust agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Разрешава на приложението да предоставя trust agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Стартиране на менюто за настройки за trust agent."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Разрешава на приложението да стартира активност, която променя поведението на trust agent."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Обвързване с услуга за trust agents"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Разрешава на приложението да се обвърже с услуга за trust agents."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Взаимодействие със системата за актуализации и възстановяване"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Избиране на година"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Избрахте <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Изтрихте <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> за работа"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Да се използва ли „Заключване в приложението“?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Със „Заключване в приложението“ екранът се заключва в едно приложение.\n\nЗа изход натиснете и задръжте бутона за скорошни приложения $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"НЕ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"СТАРТИРАНЕ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Стартиране на „Заключване в приложението“"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Изход от „Заключване в приложението“"</string>
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index ef5b1801..f5e0166 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Mode d\'avió activat"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Mode d\'avió desactivat"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Configuració"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Bloqueja ara"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet que el titular vinculi a la interfície de nivell superior d\'un fons de pantalla. No s\'hauria de necessitar mai per a les aplicacions normals."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"enllaçar amb una eina d\'interacció de veu"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet enllaçar amb la interfície de nivell superior d\'un servei d\'interacció de veu. No ha de ser mai necessari per a aplicacions normals."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestiona les frases clau en veu alta"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permet que l\'usuari gestioni les frases clau per detectar paraules actives que es diguin en veu alta. Mai no hauria de ser necessari per a les aplicacions habituals."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vincula a una pantalla remota"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permet que el titular es vinculi a la interfície de nivell superior d\'una pantalla remota. No s\'hauria de necessitar mai per a les aplicacions normals."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincula a un servei de widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permet que l\'aplicació modifiqui els paràmetres de calibratge de la pantalla tàctil. No ha de ser mai necessari per a aplicacions normals."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"accedir als certificats de DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permet que una aplicació proporcioni i utilitzi certificats de gestió de drets digitals (DRM, Digital Rights Management). No ha de ser mai necessari per a aplicacions normals."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"rebre difusions de transferència d\'entrega"</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Permet rebre informació sobre l\'estat de la transferència d\'entrega."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Definir les normes de contrasenya"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controla la longitud i els caràcters permesos a les contrasenyes de desbloqueig de pantalla."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Controlar els intents de desbloqueig de pantalla"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permet que l\'aplicació modifiqui l\'historial del navegador o els marcadors del telèfon. Això pot permetre que l\'aplicació esborri o modifiqui les dades del navegador. Nota: És possible que aquest permís no s\'apliqui a navegadors de tercers o a altres aplicacions amb capacitats de navegació web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"configuració d\'una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet que l\'aplicació defineixi una alarma en una aplicació de despertador instal·lada. És possible que algunes aplicacions de despertador no incorporin aquesta funció."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"suprimir correus de veu"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Permet que l\'aplicació suprimeixi missatges de la safata d\'entrada de la bústia de veu."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"afegeix bústia de veu"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet que l\'aplicació afegeixi missatges a la safata d\'entrada de la bústia de veu."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accedir a tots els correus de veu"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet que l\'aplicació accedeixi a tots els teus correus de veu."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifica els permisos d\'ubicació geogràfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet que l\'aplicació modifiqui els permisos d\'ubicació geogràfica del navegador. Les aplicacions malicioses poden utilitzar-ho per enviar la informació d\'ubicació a llocs web arbitraris."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifica paquets"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permet que una aplicació escolti els canvis en l\'estat de confiança."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agent de confiança"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permet que una aplicació proporcioni un agent de confiança."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Inicia el menú de configuració de l\'agent de confiança."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet que una aplicació iniciï una activitat que canviï el comportament de l\'agent de confiança."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Enllaçar amb el servei d\'un agent de confiança"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permet que una aplicació es vinculi amb el servei d\'un agent de confiança."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interacciona amb el sistema de recuperació i amb les actualitzacions"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Selecciona un any"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionat"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> suprimit"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Vols fer servir la funció Bloqueja una aplicació?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"La funció Bloqueja una aplicació bloqueja la visualització a una sola aplicació.\n\nPer sortir, mantén premut el botó d\'aplicacions recents $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INICI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Inicia la funció Bloqueja una aplicació"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Surt de la funció Bloqueja una aplicació"</string>
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 0526038..07dbeec 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Režim V letadle je ZAPNUTÝ"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Režim V letadle je VYPNUTÝ"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Nastavení"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Zamknout"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Umožňuje držiteli navázat se na nejvyšší úroveň rozhraní tapety. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"navázání na hlasovou interakci"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Umožňuje držiteli navázat se na nejvyšší úroveň rozhraní služby hlasové interakce. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"správa klíčových hlasových frází"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Umožňuje držiteli spravovat klíčové hlasové fráze pro detekci hlasových klíčových slov. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"připojit se ke vzdálenému displeji"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Umožňuje držiteli připojit se k vysokoúrovňovému rozhraní vzdáleného displeje. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"navázat se na službu widgetu"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Umožňuje aplikaci měnit parametry kalibrace dotykové obrazovky. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"přístup k certifikátům DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Umožňuje aplikaci vydávat a používat certifikáty DRM. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Příjem vysílání pro předání spojení"</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Povoluje příjem informací o stavu předání spojení."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Nastavit pravidla pro heslo"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Řídit délku hesel pro odemčení obrazovky a povolené znaky."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Sledovat pokusy o odemčení obrazovky"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Umožňuje aplikaci upravit historii prohlížeče nebo záložky uložené v telefonu. Aplikace s tímto oprávněním může vymazat či pozměnit data prohlížeče. Poznámka: Pro prohlížeče třetí strany a jiné aplikace umožňující procházení webu toto oprávnění platit nemusí."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"nastavení budíku"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikaci nastavit budík v nainstalované aplikaci budík. Některé aplikace budík tuto funkci nemusí obsahovat."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"odstraňování hlasových zpráv"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Povoluje aplikaci odstraňovat zprávy z hlasové schránky."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"přidat hlasovou zprávu"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Umožňuje aplikaci přidávat zprávy do hlasové schránky."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"číst všechny hlasové zprávy"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Umožňuje aplikaci číst všechny vaše hlasové zprávy."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"změna oprávnění prohlížeče poskytovat informace o zeměpisné poloze"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Umožňuje aplikaci upravit oprávnění funkce geolokace v prohlížeči. Škodlivé aplikace toho mohou využít k odeslání údajů o poloze na libovolné webové stránky."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ověřit balíčky"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Umožňuje aplikaci naslouchat změnám ve stavu důvěryhodnosti."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Poskytování zástupce důvěryhodnosti"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Umožňuje aplikaci poskytnout zástupce důvěryhodnosti."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Spustit nabídku nastavení agenta důvěryhodnosti"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umožňuje aplikaci spustit aktivitu, která změní chování agenta důvěryhodnosti."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vázat se na službu zástupce důvěryhodnosti"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Umožňuje aplikaci vázat se na službu zástupce důvěryhodnosti."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interakce se systémem aktualizací a obnovení"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Vybrána položka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Číslice <xliff:g id="KEY">%1$s</xliff:g> byla smazána"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Pracovní <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Použít Uzamčení v aplikaci?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Funkce Uzamčení v aplikaci uzamkne obrazovku na jedinou aplikaci.\n\nChcete-li tento režim opustit, stiskněte a podržte tlačítko posledních aplikací $."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"SPUSTIT"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Spustit Uzamčení v aplikaci"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Opustit Uzamčení v aplikaci"</string>
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 895c98e..b7805d4 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flytilstand er TIL"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flytilstand er slået FRA"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Indstillinger"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Tillader, at indehaveren kan binde en baggrunds grænseflade på øverste niveau. Dette bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"oprette binding til en tjeneste til stemmeinteraktion"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Tillader, at brugeren opretter en binding til det øverste niveau af grænsefladen i en tjeneste til stemmeinteraktion. Dette bør aldrig være nødvendigt for almindelige apps."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrer nøglesætninger til stemmeregistrering"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Tillader, at brugeren kan administrere nøglesætningerne til stemmeregistrering af kommandoord. Dette bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind til en ekstern skærm"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Tillader, at brugeren kan foretage en binding til grænsefladens øverste niveau på en ekstern skærm. Bør aldrig være nødvendigt til almindelige apps."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"forpligt til en widgettjeneste"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Tillader, at appen ændrer kalibreringsparametrene for berøringsskærmen. Dette bør aldrig være nødvendigt for almindelige apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"få adgang til DRM-certifikater"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Tillader, at en applikation leverer og anvender DRM-certfikater. Dette bør aldrig være nødvendigt for almindelige apps."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Indstil regler for adgangskode"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontroller længden samt tilladte tegn i adgangskoder til oplåsning af skærmen."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Overvåg forsøg på oplåsning af skærm"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Tillader, at appen kan ændre browserens historik eller de bogmærker, der er gemt på din telefon. Dette kan give appen tilladelse til at slette eller ændre browserdata. Bemærk! Denne tilladelse håndhæves muligvis ikke af tredjepartsbrowsere eller andre applikationer med websøgningsfunktioner."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"indstille en alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillader, at appen kan indstille en alarm i en installeret alarmapp. Nogle alarmapps har muligvis ikke denne funktion."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"tilføje telefonsvarer"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Tillader, at appen kan tilføje beskeder på din telefonsvarer."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"læs alle talebeskeder"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Tillader, at appen kan læse alle dine talebeskeder"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"skifte tilladelser til geografisk placering i Browser"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Tillader, at appen kan ændre browserens tilladelser angående geografisk placering. Ondsindede apps kan benytte dette til at sende oplysninger om placering til vilkårlige websites."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekræft pakker"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Tillader, at en applikation registrerer ændringer i trust-tilstand."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Angiv en tillidsagent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillader, at en applikation angiver en tillidsagent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Åbn indstillingsmenuen for tillidsagenten"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Giver en applikation tilladelse til at starte en aktivitet, som ændrer adfærden for tillidsagenten."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Knytte sig til en trust agent-tjeneste"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Tillader, at en applikation knytter sig til en trust agent-tjeneste."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interager med opdaterings- og gendannelsessystemet"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Vælg år"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> – arbejde"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruge Lås-til-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Med Lås-til-app låses skærmen, så den kun viser én app.\n\nHvis du vil afslutte denne tilstand, skal du trykke på knappen for seneste apps og holde fingeren nede $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lås-til-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Afslut Lås-til-app"</string>
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 35116c3..05bd32e 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flugmodus ist AN."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flugmodus ist AUS."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Einstellungen"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Jetzt sperren"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ermöglicht dem Halter, sich an die Oberfläche eines Hintergrunds auf oberster Ebene zu binden. Sollte nie für normale Apps benötigt werden."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"An einen Sprachinteraktionsdienst binden"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ermöglicht dem Inhaber, sich an die Oberfläche eines Sprachinteraktionsdienstes auf oberster Ebene zu binden. Für normale Apps sollte dies nie erforderlich sein."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Suchbegriffe für die Sprachsuche verwalten"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ermöglicht dem Inhaber die Verwaltung von Suchbegriffen für die Hotword-Erkennung bei der Sprachbedienung. Für normale Apps sollte dies nie erforderlich sein."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"An Remote-Display binden"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ermöglicht dem Halter, sich an die Oberfläche eines Remote-Displays auf oberster Ebene zu binden. Sollte für normale Apps nie benötigt werden."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"An einen Widget-Dienst binden"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Ermöglicht der App, die Kalibrierungsparameter des Touchscreens zu ändern. Für normale Apps sollte dies nie erforderlich sein."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"Auf DRM-Zertifikate zugreifen"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Ermöglicht einer App die Bereitstellung und Nutzung von DRM-Zertifikaten. Sollte für normale Apps nie benötigt werden."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Handover-Übertragungen empfangen"</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Ermöglicht das Empfangen von Informationen zum Handover-Übertragungsstatus"</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Passwortregeln festlegen"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Zulässige Länge und Zeichen für Passwörter zum Entsperren des Bildschirms festlegen"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Versuche zum Entsperren des Displays überwachen"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Ermöglicht der App, den Browserverlauf und die Lesezeichen auf Ihrem Telefon zu ändern. Damit kann die App Browserdaten löschen und ändern. Hinweis: Diese Berechtigung kann nicht von Browsern von Drittanbietern oder anderen Apps mit Internetfunktionen erzwungen werden."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"Wecker stellen"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Ermöglicht der App, einen Alarm in einer installierten Wecker-App einzurichten. Einige Wecker-Apps implementieren diese Funktion möglicherweise nicht."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"Mailboxnachrichten entfernen"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Ermöglicht der App das Entfernen von Nachrichten aus Ihrer Mailbox"</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"Mailbox-Nachrichten hinzufügen"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ermöglicht der App, Nachrichten zu Ihrem Mailbox-Posteingang hinzuzufügen"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Alle Mailboxnachrichten abrufen"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ermöglicht der App das Abrufen aller Ihrer Mailboxnachrichten"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Geolokalisierungsberechtigungen des Browsers ändern"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ermöglicht der App, die Geolokalisierungsberechtigungen des Browsers zu ändern. Schädliche Apps können so Standortinformationen an beliebige Websites senden."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Pakete überprüfen"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ermöglicht einer App die Überwachungen von Änderungen des Trust-Status"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust Agent bereitstellen"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ermöglich die Bereitstellung eines Trust Agents durch eine App"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Einstellungsmenü des Trust Agents starten"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ermöglicht einer App das Starten einer Aktivität, die das Verhalten des Trust Agents ändert"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"An Trust Agent-Service anbinden"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ermöglicht einer App die Anbindung an einen Trust Agent-Service"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Mit Update- und Wiederherstellungssystem interagieren"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Jahr auswählen"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ausgewählt"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> gelöscht"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (geschäftlich)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"\"Auf App einschränken\" verwenden?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Mit \"Auf App einschränken\" wird das Display in einer einzelnen App gesperrt.\n\nZum Beenden der Sperre berühren und halten Sie die Schaltfläche für die letzten Apps $."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"Nein"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"Starten"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"\"Auf App einschränken\" starten"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"\"Auf App einschränken\" beenden"</string>
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 0c50bb5..ea52d18 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Η λειτουργία πτήσης είναι ενεργοποιημένη."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Λειτ. πτήσης είναι ανενεργή"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Ρυθμίσεις"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Κλείδωμα τώρα"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας ταπετσαρίας. Δεν απαιτείται για συνήθεις εφαρμογές."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"σύνδεση σε παράγοντα φωνητικής αλληλεπίδρασης"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Επιτρέπει στον κάτοχο τη σύνδεση στη διεπαφή ανωτάτου επιπέδου μιας υπηρεσίας φωνητικής αλληλεπίδρασης. Δεν απαιτείται για κανονικές εφαρμογές."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"διαχείριση φωνητικών φράσεων-κλειδιών"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Δίνει στον κάτοχο τη δυνατότητα να διαχειριστεί τις φράσεις-κλειδιά για την ανίχνευση φωνητικών λέξεων-κλειδιών. Δεν απαιτείται για τις συνήθεις εφαρμογές."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"μεταφορά σε μια απομακρυσμένη οθόνη"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Επιτρέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας απομακρυσμένης οθόνης. Δεν απαιτείται ποτέ για κανονικές εφαρμογές."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"δέσμευση σε υπηρεσία γραφικών στοιχείων"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Επιτρέπει στην εφαρμογή να τροποποιεί τις παραμέτρους βαθμονόμησης της οθόνης αφής. Δεν απαιτείται για τις κανονικές εφαρμογές."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"πρόσβαση σε πιστοποιητικά DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Επιτρέπει σε μια εφαρμογή να παρέχει και να χρησιμοποιεί πιστοποιητικά DRM. Δεν θα χρειαστεί ποτέ για κανονικές εφαρμογές."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Λήψη εκπομπών μεταφοράς παράδοσης."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Επιτρέπει τη λήψη πληροφοριών κατάστασης μεταφοράς παράδοσης."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Ορισμός κανόνων κωδικού πρόσβασης"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Έλεγχος του μεγέθους και των χαρακτήρων που επιτρέπονται στους κωδικούς πρόσβασης ξεκλειδώματος οθόνης."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Παρακολούθηση προσπαθειών ξεκλειδώματος οθόνης"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Επιτρέπει στην εφαρμογή την τροποποίηση του ιστορικού του προγράμματος περιήγησης ή των σελιδοδεικτών που έχουν αποθηκευτεί στο τηλέφωνό σας. Αυτό μπορεί να δίνει τη δυνατότητα στην εφαρμογή να διαγράφει ή να τροποποιεί δεδομένα του προγράμματος περιήγησης. Σημείωση: αυτή η άδεια ίσως να μην μπορεί να εφαρμοστεί από τρίτα προγράμματα περιήγησης ή άλλες εφαρμογές με δυνατότητες περιήγησης ιστού."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ρύθμιση ξυπνητηριού"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Επιτρέπει στην εφαρμογή τη ρύθμιση μιας ειδοποίησης σε μια εγκατεστημένη εφαρμογή ξυπνητηριού. Ορισμένες εφαρμογές ξυπνητηριού ενδέχεται να μην μπορούν να ενσωματώσουν αυτήν τη λειτουργία."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"κατάργηση μηνυμάτων αυτόματου τηλεφωνητή"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Επιτρέπει στην εφαρμογή να καταργεί μηνύματα από τα εισερχόμενα του αυτόματου τηλεφωνητή σας."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"προσθήκη τηλεφωνητή"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Επιτρέπει στην εφαρμογή να προσθέτει μηνύματα στα εισερχόμενα του αυτόματου τηλεφωνητή σας."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ανάγνωση όλων των μηνυμάτων του αυτόματου τηλεφωνητή"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Επιτρέπει στην εφαρμογή να διαβάσει όλα τα μηνύματα του αυτόματου τηλεφωνητή σας."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"τροποποίηση δικαιωμάτων γεωγραφικής θέσης του Προγράμματος περιήγησης"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Επιτρέπει στην εφαρμογή την τροποποίηση των αδειών γεωτοποθεσίας του Προγράμματος περιήγησης. Τυχόν κακόβουλες εφαρμογές ενδέχεται να το χρησιμοποιήσουν για να επιτρέψουν την αποστολή πληροφοριών τοποθεσίας σε αυθαίρετους ιστότοπους."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"επαλήθευση πακέτων"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Επιτρέπει σε μια εφαρμογή να αντιλαμβάνεται τις αλλαγές στην κατάσταση εμπιστοσύνης."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Παράσχετε έναν αξιόπιστο αντιπρόσωπο."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Επιτρέπει σε μια εφαρμογή να προσφέρει έναν αξιόπιστο αντιπρόσωπο."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Εκκίνηση μενού ρυθμίσεων αξιόπιστου αντιπροσώπου."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Επιτρέπει σε μια εφαρμογή να εκκινεί μια ενέργεια που αλλάζει τη συμπεριφορά του αξιόπιστου αντιπροσώπου."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Σύνδεση σε υπηρεσία trust agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Επιτρέπει σε μια εφαρμογή να συνδεθεί σε μια υπηρεσία trust agents."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Αλληλεπίδραση με το σύστημα ενημέρωσης και ανάκτησης"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Επιλογή έτους"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Επιλέχτηκε το στοιχείο <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> διαγράφηκε"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Εργασία <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Χρήση λειτουργίας lock-to-app;"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Η λειτουργία Lock-to-app κλειδώνει την οθόνη σε μία μόνο εφαρμογή.\n\nΓια έξοδο πατήστε παρατεταμένα το κουμπί των πρόσφατων εφαρμογών $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"ΟΧΙ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ΕΝΑΡΞΗ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Έναρξη Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Έξοδος από Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 32141c3..0f3513d 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Aeroplane mode is ON"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Aeroplane mode is OFF"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Settings"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for normal applications."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind to a voice interactor"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Allows the holder to bind to the top-level interface of a voice interaction service. Should never be needed for normal apps."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"manage voice key phrases"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Allows the holder to manage the key phrases for voice hotword detection. Should never be needed for normal apps."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind to a remote display"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Allows the holder to bind to the top-level interface of a remote display. Should never be needed for normal apps."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind to a widget service"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Allows the app to modify the calibration parameters of the touch screen. Should never be needed for normal apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"access DRM certificates"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Allows an application to provision and use DRM certficates. Should never be needed for normal apps."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Receive handover transfer broadcasts."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Allows receiving handover transfer status information."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Set password rules"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Control the length and the characters allowed in screen-unlock passwords."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitor screen-unlock attempts"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"set an alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"remove voicemails"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Allows the app to remove messages from your voicemail inbox."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"add voicemail"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Allows the app to add messages to your voicemail inbox."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"read all voicemail"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Allows the app to read all your voicemails."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modify Browser geo-location permissions"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verify packages"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Allows an application to listen for changes in trust state."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Provide a trust agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Allows an application to provide a trust agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lunch trust agent settings menu."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Allows an application to lunch an activity that changes the trust agent behaviour."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind to a trust agent service"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Allows an application to bind to a trust agent service."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interact with update and recovery system"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Select year"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 32141c3..0f3513d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Aeroplane mode is ON"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Aeroplane mode is OFF"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Settings"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for normal applications."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind to a voice interactor"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Allows the holder to bind to the top-level interface of a voice interaction service. Should never be needed for normal apps."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"manage voice key phrases"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Allows the holder to manage the key phrases for voice hotword detection. Should never be needed for normal apps."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind to a remote display"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Allows the holder to bind to the top-level interface of a remote display. Should never be needed for normal apps."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind to a widget service"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Allows the app to modify the calibration parameters of the touch screen. Should never be needed for normal apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"access DRM certificates"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Allows an application to provision and use DRM certficates. Should never be needed for normal apps."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Receive handover transfer broadcasts."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Allows receiving handover transfer status information."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Set password rules"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Control the length and the characters allowed in screen-unlock passwords."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitor screen-unlock attempts"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"set an alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"remove voicemails"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Allows the app to remove messages from your voicemail inbox."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"add voicemail"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Allows the app to add messages to your voicemail inbox."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"read all voicemail"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Allows the app to read all your voicemails."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modify Browser geo-location permissions"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verify packages"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Allows an application to listen for changes in trust state."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Provide a trust agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Allows an application to provide a trust agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lunch trust agent settings menu."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Allows an application to lunch an activity that changes the trust agent behaviour."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind to a trust agent service"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Allows an application to bind to a trust agent service."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interact with update and recovery system"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Select year"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 3eb0666..2d88ca0 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"El modo avión está Activado"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"El modo avión está Desactivado"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Configuración"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite al propietario vincularse a la interfaz de nivel superior de un fondo de pantalla. Las aplicaciones normales no deben utilizar este permiso."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"vincular con un servicio de interacción por voz"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite vincular con la interfaz de nivel superior de un servicio de interacción por voz. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrar frases clave de voz"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite administrar las frases clave para detección de palabras activas de voz. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vincular a una pantalla remota"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite al propietario vincularse a la interfaz de nivel superior de una pantalla remota. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincular a un servicio de widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permite que la aplicación modifique los parámetros de calibración de la pantalla táctil. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"Acceder a certificados DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permite que una aplicación proporcione y utilice certificados DRM. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Establecer reglas de contraseña"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controlar la longitud y los caracteres permitidos en las contraseñas para desbloquear la pantalla"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Supervisa los intentos para desbloquear la pantalla"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los marcadores del navegador almacenados en el dispositivo. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"programar una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Consultar todos los mensajes del buzón de voz"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que la aplicación consulte todos los mensajes del buzón de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Verificar paquetes"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que una aplicación detecte cambios en el estado de confianza."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agente de confianza"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que una aplicación proporcione un agente de confianza."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Iniciar menú de configuración de agente de confianza"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que una aplicación inicie una actividad que modifica el comportamiento del agente de confianza."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vincular con un servicio de agente de confianza"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que una aplicación se vincule con un servicio de agente de confianza."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interaccionar con el sistema de recuperación y las actualizaciones"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> borrado"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"¿Usar bloqueo de aplicación?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"El bloqueo de aplicación bloquea la pantalla en una sola aplicación.\n\nMantén presionado el botón de aplicaciones recientes $ para salir."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar bloqueo de aplicación"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Salir de bloqueo de aplicación"</string>
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 126d20d..f5f1428 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión activado. Desactivar"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modo avión desactivado. Activar"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Ajustes"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear ahora"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite enlazar con la interfaz de nivel superior de un fondo de pantalla. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"enlazar con un servicio de interacción de voz"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite enlazar con la interfaz de nivel superior de un servicio de interacción de voz. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrar frases clave de voz"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite administrar las frases clave para la detección de palabras activas. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"enlazar a una pantalla remota"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite enlazar con la interfaz de nivel superior de una pantalla remota. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"enlazar con un servicio de widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permite que la aplicación modifique los parámetros de calibración de la pantalla táctil. No debe ser necesario para las aplicaciones normales."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"acceder a certificados DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permite que una aplicación proporcione y utilice certificados DRM. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Recibe emisiones de la transferencia de la conexión."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Permite recibir información sobre el estado de la transferencia de la conexión."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Establecimiento de reglas de contraseña"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controlar la longitud y los caracteres permitidos en las contraseñas de bloqueo de pantalla"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Control de intentos de bloqueo de pantalla"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los marcadores del navegador almacenados en el teléfono. La aplicación puede utilizar este permiso para borrar o modificar datos del navegador. Nota: este permiso no pueden utilizarlo navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"establecer una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de reloj instalada. Es posible que algunas aplicaciones de reloj no incluyan esta función."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"eliminar mensajes de voz"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Permite que la aplicación elimine mensajes de la bandeja de entrada del buzón de voz."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"añadir buzón de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación añada mensajes a la bandeja de entrada del buzón de voz."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"consultar todos los mensajes de voz"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que la aplicación consulte todos tus mensajes de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones malintencionadas pueden usar este permiso para autorizar el envío de información sobre la ubicación a sitios web arbitrarios."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar paquetes"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que una aplicación detecte cambios en el estado de confianza."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Proporcionar un agente de confianza."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que una aplicación proporcione un agente de confianza."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Abre el menú de ajustes del agente de confianza."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que una aplicación inicie una actividad que cambie el comportamiento del agente de confianza."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Enlazar con un servicio de agente de confianza"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite a una aplicación enlazar con un servicio de agente de confianza."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interactuar con el sistema de recuperación y las actualizaciones"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"¿Usar bloqueo de aplicación?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"El bloqueo de aplicación bloquea la pantalla en una sola aplicación.\n\nPara salir, mantén pulsado el botón de aplicaciones recientes $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar bloqueo de aplicación"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Salir del bloqueo de aplicación"</string>
 </resources>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 71a19f0..c73eef8 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Lennurežiim on SEES"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Lennurežiim on VÄLJAS"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Seaded"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Lukusta kohe"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lubab omanikul siduda taustapildi ülataseme liidesega. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"seo häälinteraktsiooniga"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Lubab omanikul siduda häälinteraktsiooni teenuse ülataseme liidesega. Pole kunagi vajalik tavaliste rakenduste puhul."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"häältuvastuse võtmefraaside haldus"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Võimaldab omanikul hallata häältuvastuse otsetee sõna võtmefraase. Seda ei tohiks tavaliste rakenduste puhul kunagi vaja olla."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"kaugekraaniga sidumine"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lubab omanikul siduda rakenduse kaugekraani ülataseme liidesega. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vidinateenusega sidumine"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Lubab rakendusel muuta puuteekraani kalibreerimisparameetreid. Ei tohiks kunagi olla vajalik tavaliste rakenduste puhul."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"juurdepääs DRM-i sertifikaatidele"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Lubab rakendusel ette valmistada ja kasutada DRM-i sertifikaate. Tavarakenduste puhul ei tohiks see vajalik olla."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Üleandmise edastuste vastuvõtmine"</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Võimaldab vastu võtta üleandmise olekuteavet."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Parooli reeglite määramine"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrollige ekraaniluku avamise paroolide pikkust ja tähemärke."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Ekraani avamiskatsed"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Võimaldab rakendusel muuta telefoni salvestatud brauseri ajalugu või järjehoidjaid. See võimaldab rakendusel kustutada või muuta brauseri andmeid. Märkus: see luba ei pruugi jõustuda kolmanda osapoole brauserites või teistes veebisirvimisvõimega rakendustes."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"määrake äratus"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Võimaldab rakendusel seada installitud äratuskellarakenduses äratuse. Mõned äratuskellarakendused ei pruugi seda funktsiooni juurutada."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"kõnepostisõnumite eemaldamine"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Võimaldab rakendusel teie kõneposti postkastist sõnumeid eemaldada."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"lisa kõneposti"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Võimaldab rakendusel lisada sõnumeid teie kõneposti postkasti."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"kogu kõneposti lugemine"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Võimaldab rakendusel kogu kõneposti lugeda."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Brauseri geolokatsiooniõiguste muutmine"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Võimaldab rakendusel muuta brauseri geolokatsiooniõigusi. Pahatahtlikud rakendused võivad seda kasutada asukohateabe saatmise lubamiseks suvalistele veebisaitidele."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakettide kinnitamine"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Lubab rakendusel tuvastada muudatusi usaldusväärses olekus."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Usaldusväärse agendi esitamine."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Võimaldab rakendusel esitada usaldusväärset agenti."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Käivita usaldusväärse agendi seadete menüü."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Võimaldab rakendusel käivitada tegevuse, mis muudab usaldusväärse agendi käitumist."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Usaldusväärse agendi teenusega sidumine"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Lubab rakendusel ennast siduda usaldusväärse agendi teenusega."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Suhtlemine värskenduse ja taastesüsteemiga"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Aasta valimine"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valitud"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> on kustutatud"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Töö <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Kas soovite kasutada rakendusele lukustamist?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Rakendusele lukustamise funktsioon lukustab kuva ühele rakendusele.\n\nVäljumiseks hoidke hiljutiste rakenduste nuppu $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"EI"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"KÄIVITA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Käivita rakendusele lukustamine"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Välju rakendusele lukustamisest"</string>
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 8cf01d9..da802cf 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"حالت هواپیما روشن است"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"حالت هواپیما خاموش است"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"تنظیمات"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"اکنون قفل شود"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"‏به دارنده اجازه می‎دهد تا به رابط سطح بالای تصویر زمینه متصل شود. برنامه‎های معمولی هرگز به این ویژگی نیاز ندارند."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"مقید بودن به سرویس تعامل صوتی"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"به دارنده امکان می‌دهد به واسط سطح بالای سرویس تعامل صوتی مقید باشد. برای برنامه‌های عادی هرگز نباید لازم باشد."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"مدیریت عبارات کلیدی صوتی"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"به دارنده امکان می‌دهد عبارات کلیدی برای شناسایی کلیدگفته صوتی را مدیریت کند. هرگز نباید برای برنامه‌های معمولی مورد نیاز باشد."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"اتصال به نمایشگر راه دور"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"به دارنده ام‍ک‍ان می‌دهد تا به رابط سطح بالای نمایشگر راه دور وصل شود. نباید هرگز برای برنامه‌های عادی لازم باشد."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"اتصال به یک سرویس ابزارک"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"به برنامه امکان می‌دهد پارامترهای کالیبراسیون صفحه لمسی را تغییر دهد. هرگز نباید برای برنامه‌های عادی مورد نیاز باشد."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"‏دسترسی به گواهی‌های DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"‏به یک برنامه کاربردی اجازه ارائه مجوز و استفاده از گواهی‌های DRM را می‌دهد. هرگز برای برنامه‌های عادی مورد نیاز نیست."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"دریافت پخش‌های انتقال تحویلی."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"دریافت اطلاعات وضعیت انتقالی را ممکن می‌سازد."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"تنظیم قوانین رمز ورود"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"‏طول و نویسه‎های مجاز در گذرواژه‌های بازکردن قفل صفحه را کنترل کنید."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"نمایش تلاش‌های قفل گشایی صفحه"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"به برنامه اجازه می‌دهد سابقه مرورگر یا نشانک‌های ذخیره شده در تلفن شما را اصلاح کند. این ویژگی ممکن است به برنامه اجازه دهد داده‌های مرورگر را حذف یا اصلاح کند. توجه: این مجوز ممکن است توسط مرورگرهای شخص ثالث یا سایر برنامه‌های دارای قابلیت مرور وب قابل اجرا نباشد."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"تنظیم یک هشدار"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"‏به برنامه اجازه می‎دهد تا هشداری را در برنامه ساعت زنگدار نصب شده تنظیم کند. برخی از برنامه‎های ساعت زنگدار نمی‎توانند این ویژگی را اعمال کنند."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"حذف پست‌های صوتی"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"به برنامه امکان می‌دهد پیام‌ها را از صندوق ورودی پست صوتی شما حذف کند."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"افزودن پست صوتی"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"به برنامه اجازه می‌دهد تا پیام‌ها را به صندوق دریافت پست صوتی شما اضافه کند."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"خواندن کل پست صوتی"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"به برنامه اجازه می‌دهد همه پست‌های صوتی‌تان را بخواند."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"تغییر مجوزهای مکان جغرافیایی مرورگر"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"‏به برنامه اجازه می‎دهد تا مجوزهای جغرافیایی مرورگر را تغییر دهد. برنامه‎های مخرب می‎توانند از آن استفاده کنند تا اطلاعات موقعیت مکانی را به سایت‌های وب کتابخانه بفرستند."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"تأیید بسته‌ها"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"‏به یک برنامه کاربردی برای گوش دادن به تغییرات در trust اجازه می‌دهد."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"یک عامل مورد اعتماد فراهم می‌آورد."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"به برنامه امکان می‌دهد یک عامل مورد اعتماد فراهم آورد."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"منوی تنظیمات نماینده امانی را راه‌اندازی کنید."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"به برنامه اجازه می‌دهد تا فعالیتی را راه‌اندازی کند که رفتار نماینده امانی را تغییر می‌دهد."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"‏اتصال به یک سرویس trust agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"‏به یک برنامه کاربردی برای اتصال به یک سرویس trust agent اجازه می‌دهد."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"تعامل با سیستم به‌روزرسانی و بازیابی"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"انتخاب سال"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> انتخاب شد"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> حذف شد"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> محل کار"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"از «قفل برنامه» استفاده شود؟"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"«قفل برنامه» نمایشگر را در یک برنامه قفل می‌کند.\n\nبرای خروج، دکمه برنامه‌های جدید $ را فشار داده و نگه‌دارید"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"خیر"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"شروع"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"راه‌اندازی «قفل برنامه»"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج از «قفل برنامه»"</string>
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 2852844..9e6cafd 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Lentokonetila on KÄYTÖSSÄ"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Lentokonetila on POIS KÄYTÖSTÄ"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Asetukset"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Lukitse nyt"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Antaa sovelluksen sitoutua taustakuvan ylätason käyttöliittymään. Ei tavallisten sovellusten käyttöön."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"puheohjauspalveluun sitominen"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Antaa sovelluksen luoda sidoksen puheohjauspalvelun ylätason rajapintaan. Ei tavallisten sovelluksien käyttöön."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"puhuttujen ilmausten hallinta"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Sallii ilmausten hallinnan puhuttujen toimintosanojen tunnistusta varten. Ei tavallisten sovellusten käyttöön."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"etänäyttöön sitoutuminen"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Antaa sovelluksen sitoutua etänäytön ylemmän tason käyttöliittymään. Ei tavallisten sovelluksien käyttöön."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"sitoudu widget-palveluun"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Antaa sovelluksen muokata kosketusnäytön kalibrointiparametreja. Ei tavallisten sovellusten käyttöön."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM-varmenteiden käyttö"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Antaa sovelluksen käyttää DRM-varmenteita ja hallita niiden käyttäjiä. Ei tavallisten sovellusten käyttöön."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Vastaanota handover-siirtolähetyksiä."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Sallii handover-siirtotietojen vastaanottamisen."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Aseta salasanasäännöt"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Hallinnoi ruudun lukituksenpoistosalasanoissa sallittuja merkkejä ja salasanan pituutta."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Tarkkaile ruudun lukituksen poistoyrityksiä"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Antaa sovelluksen muokata selaimen historiaa ja puhelimeen tallennettuja kirjanmerkkejä. Sovellus voi poistaa tai muokata selaimen tietoja. Huomaa: kolmannen osapuolen selaimet tai muut sovellukset, jotka pystyvät selaamaan verkkoa, eivät saa käyttää tätä lupaa."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"aseta herätys"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Antaa sovelluksen asettaa hälytyksen sisäiseen herätyskellosovellukseen. Jotkin herätyskellosovellukset eivät välttämättä käytä tätä ominaisuutta."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"vastaajaviestien poistaminen"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Antaa sovelluksen poistaa viestejä saapuneista vastaajaviesteistä."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"lisää vastaajaviesti"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Antaa sovelluksen lisätä viestejä saapuneisiin vastaajaviesteihin."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"kaikkien vastaajaviestien luku"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Antaa sovelluksen lukea kaikki vastaajaviestisi."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"selaimen maantieteellisen sijainnin lupien muokkaaminen"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Antaa sovelluksen muokata Selaimen maantieteellisen sijainnin lupia. Haitalliset sovellukset voivat sallia tällä sijaintitietojen lähettämisen mielivaltaisiin sivustoihin."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vahvista paketteja"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Antaa sovelluksen seurata luottamuksen tilamuutoksia."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Luotettavan tahon tarjoaminen"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Antaa sovelluksen tarjota luotettavan tahon."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Käynnistä luotettavan tahon asetusvalikko."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Sallii sovelluksen käynnistää toiminnon, joka muuttaa luotettavan tahon toimintaa."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Luotettavaan tahoon sitoutuminen"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Antaa sovelluksen sitoutua luotettavaan tahoon."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Vuorovaikutus päivitys- ja palautusjärjestelmän kanssa"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Valitse vuosi"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valittu"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> poistettiin"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (työ)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Käytetäänkö sovellukseen lukitusta?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Sovellukseen lukitus lukitsee näytön yhteen sovellukseen.\n\nPoistu pitämällä viimeaikaisten sovellusten painiketta $ painettuna."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"EI"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ALOITA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Aloita sovellukseen lukitus"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Lopeta sovellukseen lukitus"</string>
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index f3e6322..c23d9aa 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Le mode Avion est activé."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Le mode Avion est désactivé."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Paramètres"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un fond d\'écran. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"s\'associer à un service d\'interaction vocale"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet à l\'application de s\'associer à l\'interface de niveau supérieur d\'un service d\'interaction vocale. Ne devrait pas être nécessaire pour les applications standards."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gérer les phrases clés vocales"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permet à l\'utilisateur de gérer les phrases clés pour la détection de mots clés vocaux. Cette option ne devrait jamais être nécessaire pour les applications standards."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"lier à un écran distant"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un écran distant. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"s\'associer à un service de widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permet à l\'application de modifier les paramètres de calibrage de l\'écran tactile. Ne devrait jamais être nécessaire pour les applications standards."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"accéder aux certificats GDN"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permet à une application de fournir et d\'utiliser les certificats de GDN. Cela ne devrait jamais être nécessaire pour les applications normales."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Définir les règles du mot de passe"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Choisir le nombre et le type de caractères autorisés dans les mots de passe de déverrouillage de l\'écran"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Gérer les tentatives de déverrouillage de l\'écran"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permet à l\'application de modifier l\'historique du navigateur ou les favoris enregistrés sur votre téléphone. Cette autorisation peut lui permettre d\'effacer ou de modifier les données du navigateur. Remarque : il est possible que cette autorisation ne soit pas appliquée par les navigateurs tiers ni par d\'autres applications permettant de naviguer sur le Web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"définir une alarme"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet à l\'application de régler la sonnerie d\'une fonction de réveil installée sur votre appareil. Cette fonctionnalité n\'est pas compatible avec toutes les applications de réveils."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ajouter des messages vocaux"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet à l\'application d\'ajouter des messages à votre messagerie vocale."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accéder à tous les messages vocaux"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet à l\'application d\'accéder à tous vos messages vocaux."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifier les autorisations de géolocalisation du navigateur"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet à l\'application de modifier les autorisations de géolocalisation du navigateur. Des applications malveillantes peuvent exploiter cette fonctionnalité pour permettre l\'envoi de données de localisation à des sites Web arbitraires."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vérifier les paquets"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permet à une application de détecter les modifications de l\'état de confiance."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fournir un agent de confiance."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permet à une application de fournir un agent de confiance."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancer le menu des paramètres de l\'agent de confiance."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet à une application de lancer une activité qui modifie le comportement de l\'agent de confiance."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Lier à un service d\'agent de confiance"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permet à une application de se lier à un service d\'agent de confiance."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interagir avec le système de récupération et de mise à jour"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Sélectionnez une année"</string>
     <string name="item_is_selected" msgid="949687401682476608">"« <xliff:g id="ITEM">%1$s</xliff:g> » a été sélectionné"</string>
     <string name="deleted_key" msgid="7659477886625566590">"« <xliff:g id="KEY">%1$s</xliff:g> » a été supprimé"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Utiliser la fonctionnalité de verrouillage sur l\'application?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"La fonctionnalité « Verrouiller sur l\'application » verrouille l\'écran sur une seule application.\n\nPour quitter ce mode, maintenez le doigt sur le bouton Applications récentes $."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NON"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"COMMENCER"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Lancer Verrouiller sur l\'application"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Quitter Verrouiller sur l\'application"</string>
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index cfb37c2..5544a45 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Le mode Avion est activé."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Le mode Avion est désactivé."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Paramètres"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permet à l\'application autorisée de s\'associer à l\'interface de plus haut niveau d\'un fond d\'écran. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"s\'associer à un service d\'interaction vocale"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permet à l\'application de s\'associer à l\'interface de niveau supérieur d\'un service d\'interaction vocale. Ne devrait pas être nécessaire pour les applications standards."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gérer les expressions clés vocales"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permettre à l\'application autorisée de gérer les expressions clés pour la détection de mots clés vocaux. Ne devrait jamais être nécessaire pour les applications standards."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"s\'associer à un écran à distance"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permettre à l\'application autorisée de s\'associer à l\'interface de niveau supérieur d\'un écran à distance. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"associer à un service widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permettre à l\'application de modifier les paramètres de calibrage de l\'écran tactile. Ne devrait jamais être nécessaire pour les applications standards."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"accéder aux certificats de GDN"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permettre à une application de fournir et d\'utiliser des certificats de GDN. Ne devrait jamais être nécessaire pour les applications standards."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Définir les règles du mot de passe"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Choisir le nombre et le type de caractères autorisés dans les mots de passe de déverrouillage de l\'écran"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Gérer les tentatives de déverrouillage de l\'écran"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permet à l\'application de modifier l\'historique du navigateur ou les favoris enregistrés sur votre téléphone. Cette autorisation peut lui permettre d\'effacer ou de modifier les données du navigateur. Remarque : il est possible que cette autorisation ne soit pas appliquée par les navigateurs tiers ni par d\'autres applications permettant de naviguer sur le Web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"définir une alarme"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permet à l\'application de régler la sonnerie d\'un réveil installé. Cette fonctionnalité n\'est pas disponible sur tous les réveils."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ajouter un message vocal"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet à l\'application d\'ajouter des messages à votre messagerie vocale."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accéder à tous les messages vocaux"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet à l\'application d\'accéder à tous vos messages vocaux."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifier les autorisations de géolocalisation du navigateur"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet à l\'application de modifier les autorisations de géolocalisation du navigateur. Des applications malveillantes peuvent exploiter cette fonctionnalité pour permettre l\'envoi de données de localisation à des sites Web arbitraires."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vérifier les packages"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permettre à une application de détecter les modifications de l\'état de confiance."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fournir un agent de confiance"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permettre à une application de fournir un agent de confiance"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancer le menu des paramètres de l\'agent de confiance"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permet à une application de lancer une activité qui modifie le comportement de l\'agent de confiance."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"S\'associer à un service d\'agent de confiance"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permettre à une application de s\'associer à un service d\'agent de confiance."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interagir avec le système de récupération et de mise à jour"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Sélectionner une année"</string>
     <string name="item_is_selected" msgid="949687401682476608">"\"<xliff:g id="ITEM">%1$s</xliff:g>\" sélectionné"</string>
     <string name="deleted_key" msgid="7659477886625566590">"\"<xliff:g id="KEY">%1$s</xliff:g>\" supprimé"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Utiliser la fonctionnalité Verrouiller sur l\'application ?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"La fonctionnalité Verrouiller sur l\'application verrouille l\'écran sur une seule application.\n\nPour quitter ce mode, appuyez de manière prolongée sur le bouton Applications récentes $."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NON"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ACTIVER"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Activer Verrouiller sur l\'application"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Quitter Verrouiller sur l\'application"</string>
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index bf22360..379a318 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"हवाई जहाज मोड चालू है"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"हवाई जहाज मोड बंद है"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"सेटिंग"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"अभी लॉक करें"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"धारक को किसी वॉलपेपर के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"किसी ध्वनि सहभागिताकर्ता से आबद्ध हों"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"धारक को किसी ध्वनि सहभागिता सेवा के शीर्ष-स्तर के इंटरफ़ेस से आबद्ध होने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ध्वनि कीफ़्रेज़ प्रबंधित करें"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"धारक को ध्वनि हॉटवर्ड पहचान के लिए कीफ़्रेज़ प्रबंधित करने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"रिमोट डिस्प्ले से आबद्ध करें"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"धारक को किसी रिमोट डिस्प्ले के शीर्ष-स्‍तरीय इंटरफ़ेस से आबद्ध होने देती है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"किसी विजेट सेवा से आबद्ध करें"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"ऐप्स को टच स्क्रीन के कैलिब्रेशन पैरामीटर को बदलने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM प्रमाणपत्र एक्सेस करें"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"एप्लिकेशन को DRM प्रमाणपत्रों का प्रावधान और उपयोग करने देती है. सामान्य ऐप्स के लिए कभी भी आवश्यकता नहीं होना चाहिए."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"हस्तांतरण स्थानान्तरण प्राप्त करें."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"हस्तांतरण स्थानान्तरण स्थिति की जानकारी प्राप्त करने देती है."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"पासवर्ड नियम सेट करें"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"स्‍क्रीन-अनलॉक पासवर्ड में अनुमति प्राप्त लंबाई और वर्णों को नियंत्रित करें."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"स्‍क्रीन-अनलॉक के प्रयासों पर निगरानी रखें"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"ऐप्स  को आपके फ़ोन में संग्रहीत ब्राउज़र के इतिहास या बुकमार्क को संशोधित करने देता है. इससे ऐप्स  ब्राउज़र डेटा को मिटा सकता है या संशोधित कर सकता है. ध्‍यान दें: यह अनुमति तृतीय-पक्ष ब्राउज़र या वेब ब्राउज़िंग क्षमताओं वाले अन्‍य ऐप्स  द्वारा लागू नहीं की जा सकती."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"अलार्म सेट करें"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"ऐप्स को इंस्‍टॉल किए गए अलार्म घड़ी ऐप्स में अलार्म सेट करने देता है. हो सकता है कुछ अलार्म घड़ी ऐप्स में यह सुविधा न हो."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"ध्वनिमेल निकालें"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"ऐप्स को आपके ध्वनिमेल इनबॉक्स में से संदेशों को निकालने देती है."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ध्‍वनिमेल जोड़ें"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ऐप्स  को आपके ध्‍वनिमेल इनबॉक्‍स में संदेश जोड़ने देता है."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"सभी ध्वनिमेल पढ़ें"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ऐप्स को आपके सभी ध्वनिमेल पढ़ने देती है."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउज़र भौगोलिक-स्थान अनुमतियों को बदलें"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ऐप्स को ब्राउज़र के भौगोलिक-स्‍थान की अनुमतियां संशोधित करने देता है. दुर्भावनापूर्ण ऐप्स इसका उपयोग एकपक्षीय वेबसाइट को स्‍थान जानकारी भेजने में कर सकते हैं."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"पैकेज सत्‍यापि‍त करें"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"किसी एप्लिकेशन को ट्रस्ट स्थिति के बदलावों को सुनने की अनुमति देती है."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"विश्वसनीय एजेंट प्रदान करें."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"एप्लिकेशन को विश्वसनीय एजेंट प्रदान करने देती है."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"विश्वस्त एजेंट सेटिंग मेनू लॉन्च करें."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"एप्लिकेशन को ऐसी गतिविधि लॉन्च करने की अनुमति मिलती है जो विश्वस्त एजेंट के व्यवहार में बदलाव लाती है."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ट्रस्ट एजेंट सेवा से आबद्ध करना"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"किसी एप्लिकेशन को ट्रस्ट एजेंट सेवा से आबद्ध करने की अनुमति देती है."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"अपडेट और पुनर्प्राप्ति सिस्टम के साथ सहभागिता करें"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"वर्ष चुनें"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> चयनित"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> को हटा दिया गया"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"कार्यस्थल का <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"ऐप्स-पर-लॉक करें का उपयोग करें?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"ऐप्स-पर-लॉक करें, डिस्प्ले को किसी एकल ऐप्स में लॉक कर देता है.\n\nबाहर निकलने के लिए हाल ही के ऐप्स बटन $ को दबाए रखें"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"नहीं"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"प्रारंभ करें"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"ऐप्स-पर-लॉक करें प्रारंभ करें"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"ऐप्स-पर-लॉक करें से बाहर निकलें"</string>
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index c5da5d8..a906ab5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Uključen je način rada u zrakoplovu"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Isključen je način rada u zrakoplovu"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Postavke"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Nositelju omogućuje povezivanje sa sučeljem pozadinske slike najviše razine. Ne bi smjelo biti potrebno za normalne aplikacije."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"povezivanje s uslugom glasovne interakcije"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Vlasniku omogućuje povezivanje sa sučeljem najviše razine usluge glasovne interakcije. Nije potrebno za normalne aplikacije."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"upravljanje glasovnim ključnim frazama"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Omogućuje korisniku upravljanje ključnim frazama za otkrivanje glasovnih pokretača značajke. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"vezanje uz udaljeni zaslon"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Nositelju omogućuje vezanje uza sučelje najviše razine udaljenog zaslona. Ne bi smjelo biti potrebno za normalne aplikacije."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vezanje na uslugu widgeta"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Omogućuje aplikaciji izmjenu parametara kalibracije dodirnog zaslona. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"pristup DRM certifikatima"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Aplikaciji omogućuje pružanje i korištenje DRM certifikata. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Postavi pravila zaporke"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Upravljajte duljinom zaporki za otključavanje zaslona i dopuštenim znakovima u tim zaporkama."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Nadgledaj pokušaje otključavanja zaslona"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Aplikaciji omogućuje izmjenu povijesti i oznaka Preglednika pohranjenih na telefonu. To aplikaciji može omogućiti brisanje ili izmjenu podataka Preglednika. Napomena: tu dozvolu ne mogu primijeniti preglednici treće strane ili druge aplikacije s mogućnostima pregledavanja weba."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"postavljanje alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Omogućuje aplikaciji postavljanje alarma na instaliranoj aplikaciji budilici. Neke aplikacije budilice možda neće primijeniti tu značajku."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodaj govornu poštu"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Omogućuje aplikaciji da doda poruke u vašu govornu poštu."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"čitanje svih poruka u govornoj pošti"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Aplikaciji omogućuje čitanje svih vaših poruka u govornoj pošti."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"izmjena dozvola za geolociranje u pregledniku"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Omogućuje aplikaciji promjenu geolokacijskih dozvola preglednika. Zlonamjerne aplikacije mogu to upotrijebiti da bi dopustile slanje podataka o lokaciji nasumičnim web-lokacijama."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"provjeri pakete"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Omogućuje aplikaciji praćenje promjena pouzdanog stanja."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Pružanje agenta za pouzdanost."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Omogućuje aplikaciji pružanje agenta za pouzdanost."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Pokretanje izbornika postavki agenta za pouzdanost."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Aplikaciji omogućuje pokretanje aktivnosti koja mijenja ponašanje agenta za pouzdanost."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Povezivanje s uslugom pouzdanog predstavnika"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Omogućuje aplikaciji povezivanje s uslugom pouzdanog predstavnika."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interakcija s ažuriranjem i sustavom za oporavak"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Odaberite godinu"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Odabrana je stavka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Izbrisan je broj <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za posao"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Želite li upotrijebiti zaključavanje na aplikaciju?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Značajkom zaključavanja na aplikaciju zaključava se prikaz na pojedinačnu aplikaciju.\n\nDa biste je zatvorili, pritisnite i držite gumb za nedavne aplikacije $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"POKRENI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Pokretanje zaključavanja na aplikaciju"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Zatvaranje zaključavanja na aplikaciju"</string>
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 414941f..931b1f5 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Repülőgép üzemmód bekapcsolva"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Repülőgép üzemmód kikapcsolva"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Beállítások"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Zárolás most"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lehetővé teszi, hogy a tulajdonos kötelezővé tegye egy háttérkép legfelső szintű felületét. A normál alkalmazásoknak erre soha nincs szüksége."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"csatlakozás egy hangvezérlőhöz"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Lehetővé teszi a használó számára, hogy csatlakozzon egy hangvezérlő szolgáltatás legfelső szintű kezelőfelületéhez. A normál alkalmazásoknak erre soha nincs szükségük."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"hangalapú kulcskifejezések kezelése"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Az engedély birtokosa kezelheti a hangalapú hotwordök felismerésére szolgáló kulcskifejezéseket. Az átlagos alkalmazásoknak nem lehet rá szükségük."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"csatlakozás egy távoli kijelzőhöz"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lehetővé teszi a használó számára, hogy csatlakozzon egy távoli kijelző legfelső szintű kezelőfelületéhez. A normál alkalmazásoknak erre soha nincs szükségük."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"csatlakozás modulszolgáltatáshoz"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Lehetővé teszi, hogy az alkalmazás módosítsa az érintőképernyő kalibrációs paramétereit. A normál alkalmazásoknál erre elvileg soha nincs szükség."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM-tanúsítványokhoz való hozzáférés"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Engedélyezi egy alkalmazás számára a DRM-tanúsítványokhoz való hozzáférést és azok használatát. Átlagos alkalmazásoknak erre nem lehet szükségük."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Az átadás átviteli adásainak fogadása."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Lehetővé teszi az átadás átviteli állapotinformációinak fogadását."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Jelszavakkal kapcsolatos szabályok beállítása"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"A képernyőzár-feloldási jelszavakban engedélyezett karakterek és hosszúság vezérlése."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Képernyőzár-feloldási kísérletek figyelése"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a böngésző előzményeit vagy a telefonon tárolt könyvjelzőket. Az engedéllyel rendelkező alkalmazás törölheti vagy módosíthatja a böngésző adatait. Megjegyzés: előfordulhat, hogy ezt az engedélyt harmadik felek által üzemeltetett böngészők vagy egyéb böngészésre képes alkalmazások nem léptetik életbe."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ébresztés beállítása"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Lehetővé teszi az alkalmazás számára, hogy ébresztőt állítson be egy telepített ébresztőóra alkalmazásban. Egyes ilyen alkalmazásokban lehet, hogy nem működik ez a funkció."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"hangpostaüzenetek eltávolítása"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Lehetővé teszi az alkalmazás számára, hogy üzeneteket távolítson el a bejövő hangpostafiókból."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"hangposta hozzáadása"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Lehetővé teszi az alkalmazás számára, hogy üzeneteket adjon hozzá bejövő hangpostájához."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"az összes hangüzenet olvasása"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Engedélyezi az alkalmazásnak az összes hangüzenet olvasását."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"a böngésző helymeghatározási engedélyeinek módosítása"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a böngésző helymeghatározási engedélyeit. Rosszindulatú alkalmazások ezt arra használhatják, hogy a helyére vonatkozó információkat küldjenek tetszőleges webhelyeknek."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"csomagok ellenőrzése"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Lehetővé teszi, hogy az alkalmazás figyelje a trust-állapot változásait."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust agent szoftver megadása"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Lehetővé teszi, hogy az alkalmazás megadjon egy trust agent szoftvert."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Trust agent szoftver beállításmenüjének indítása."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Lehetővé teszi az alkalmazások számára olyan tevékenységek indítását, amelyek megváltoztatják a trust agent szoftver viselkedését."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Csatlakozás egy trust agent szolgáltatáshoz"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Lehetővé teszi, hogy az alkalmazás egy trust agent szolgáltatáshoz csatlakozzon."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Kapcsolatfelvétel a frissítési és helyreállítási rendszerrel"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Válassza ki az évet"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kiválasztva"</string>
     <string name="deleted_key" msgid="7659477886625566590">"A(z) <xliff:g id="KEY">%1$s</xliff:g> érték törölve"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Munkahelyi <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Használni szeretné az alkalmazászárolást?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Az alkalmazászárolás lezárja a kijelzőt az adott alkalmazásban.\n\nA kilépéshez tartsa lenyomva a legutóbbi alkalmazások gombot ($)."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEM"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INDÍT"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Az alkalmazászárolás indítása"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Kilépés az alkalmazászárolásból"</string>
 </resources>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 60b55a1..8e36c85 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Ինքնաթիռային ռեժիմը միացված է"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Ինքնաթիռային ռեժիմը անջատված է"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Կարգավորումներ"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Թույլ է տալիս սեփականատիրոջը միանալ պաստառի վերին մակարդակի ինտերֆեյսին: Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի:"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"կապվել ձայնային փոխազդիչին"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Թույլ է տալիս սեփականատիրոջը միանալ ձայնային փոխազդիչի բազային միջերեսին: Սովորական ծրագրերի համար երբևէ չպետք է անհրաժեշտ լինի:"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"կառավարել ձայնային բանալի բառակապակցությունները"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Սեփականատիրոջը թույլ է տալիս կառավարել բանալի բառակապակցությունները՝ ձայնային թեժ բառերի հայտնաբերման համար: Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի:"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"միանալ հեռակա էկրանին"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Թույլ է տալիս սեփականատիրոջը միանալ հեռակա էկրանի վերին մակարդակի ինտերֆեյսին: Սովորական ծրագրերի համար երբևէ չպետք է անհրաժեշտ լինի:"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"միանալ վիջեթ ծառայությանը"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Թույլ է տալիս ծրագրին փոփոխել հպէկրանի չափաբերման կարգավորումները: Սովորական ծրագրերի համար երբեք պետք չի գալու:"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM հավաստագրերի մատչում"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Ծրագրին թույլ է տալիս տրամադրել և օգտագործել DRM վկայագրեր: Սովորական ծրագրերի համար երբեք պետք չի գալիս:"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Սահմանել գաղտնաբառի կանոնները"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Վերահսկել էկրանի ապակողպման գաղտնաբառերի թույլատրելի երկարությունն ու գրանշանները:"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Վերահսկել էկրանի ապակողպման փորձերը"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Թույլ է տալիս հավելվածին փոփոխել դիտարկչի պատմությունը կամ ձեր հեռախոսում պահված էջանիշերը: Այն կարող է թույլ տալ հավելվածին ջնջել կամ փոփոխել դիտարկչի տվյալները: Նշում. այս թույլտվությունը չի կարող գործածվել կողմնակի դիտարկիչների կամ վեբ զննարկման հնարավորություններով այլ հավելվածների կողմից:"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"դնել ազդանշան"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Թույլ է տալիս հավելվածին սահմանել զարթուցիչի ծրագրում տեղադրված ազդանշանը: Զարթուցիչի որոշ հավելվածներ չեն կարող կիրառել այս հատկությունը:"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ավելացնել ձայնային փոստ"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Թույլ է տալիս հավելվածին ավելացնել հաղորդագրություններ ձեր ձայնային փոստի արկղում:"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"կարդալ ձայնային հաղորդագրությունները"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Թույլ է տալիս հավելվածին կարդալ ձեր բոլոր ձայնային հաղորդագրությունները:"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"փոփոխել դիտարկչի աշխարհագրական տեղանքի թույլտվությունները"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Թույլ է տալիս հավելվածին փոփոխել զննարկչի աշխարհագրական դիրքի թույլտվությունները: Վնասարար հավելվածները կարող են օգտագործել սա` թույլատրելու ուղարկել տեղադրության վերաբերյալ տեղեկությունները կամայական վեբ կայքերին:"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"հաստատել փաթեթները"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ծրագրին թույլ է տալիս լսել վստահության կարգավիճակի փոփոխությունները:"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Տրամադրել վստահելի գործակալ:"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ծրագրին թույլ է տալիս տրամադրել վստահելի գործակալ:"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Գործարկել վստահելի գործակալի կարգավորումների ցանկը:"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ծրագրին թույլ է տալիս իրականացնել այնպիսի գործունեություն, որը փոխում է վստահելի գործակալի վարքագիծը:"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Կապվել վստահելի գործակալի ծառայությանը"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ծրագրին թույլ է տալիս կապվել վստահելի գործակալի ծառայությանը:"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Փոխազդել թարմացման և վերականգնման համակարգի հետ"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Ընտրեք տարին"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Ընտրված է <xliff:g id="ITEM">%1$s</xliff:g> տարրը"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> թիվը ջնջված է"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Աշխատանքային <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Օգտագործե՞լ lock-to-app գործառույթը:"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app գործառույթը արգելափակում է ցուցադրումը առանձին ծրագրերում:\n\nԳործառույթն անջատելու համար սեղմեք և պահեք վերջին ծրագրերի կոճակը $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"Ոչ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"Այո"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Միացնել Lock-to-app գործառույթը"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Անջատել Lock-to-app գործառույթը"</string>
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 2033596..fd164cc 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Mode pesawat AKTIF"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Mode pesawat MATI"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Setelan"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Kunci sekarang"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Mengizinkan pemegang mengikat antarmuka tingkat tinggi dari suatu wallpaper. Tidak pernah diperlukan oleh apl normal."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"mengikat ke pemicu interaksi suara"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Memungkinkan pemegang mengikat antarmuka tingkat tinggi dari layanan interaksi suara. Tidak pernah diperlukan oleh aplikasi normal."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"mengelola frasa kunci suara"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Memungkinkan pemegang mengelola frasa kunci untuk deteksi kata cepat suara. Tidak pernah diperlukan oleh aplikasi normal."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"mengikat ke layar jarak jauh"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Mengizinkan pemegang mengikat ke antarmuka tingkat atas dari layar jarak jauh. Tidak pernah diperlukan untuk aplikasi normal."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"mengikat ke layanan widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Memungkinkan aplikasi mengubah parameter kalibrasi layar sentuh. Tidak diperlukan oleh aplikasi normal."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"mengakses sertifikat DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Memungkinkan aplikasi menyediakan dan menggunakan sertifikat DRM. Tidak pernah dibutuhkan untuk aplikasi normal."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Terima siaran transfer penyerahan."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Memungkinkan penerimaan informasi status transfer penyerahan."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Setel aturan sandi"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrol panjang dan karakter yang diizinkan dalam sandi pembuka layar."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Upaya pembukaan kunci layar monitor"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Memungkinkan aplikasi mengubah riwayat atau bookmark Browser yang tersimpan dalam ponsel Anda. Izin ini memungkinkan aplikasi menghapus atau mengubah data Browser. Catatan: izin ini tidak dapat diberlakukan oleh browser pihak ketiga atau aplikasi lain dengan kemampuan menjelajahi web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"setel alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Mengizinkan apl menyetel alarm di apl jam alarm yang terpasang. Beberapa apl jam alarm mungkin tidak menerapkan fitur ini."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"buang pesan suara"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Memungkinkan aplikasi membuang pesan dari kotak masuk pesan suara."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"tambahkan kotak pesan"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Mengizinkan apl menambahkan pesan ke kotak masuk untuk pesan suara Anda."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"baca semua kotak pesan"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Mengizinkan aplikasi membaca semua kotak pesan Anda."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"memodifikasi izin geolokasi Browser"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Mengizinkan apl memodifikasi izin geolokasi Browser. Apl berbahaya dapat menggunakan izin ini untuk memungkinkan pengiriman informasi lokasi ke sembarang situs web."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifikasi paket"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Mengizinkan aplikasi mendengarkan perubahan dalam status kepercayaan."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Berikan agen tepercaya."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Memungkinkan aplikasi memberikan agen tepercaya."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Luncurkan menu setelan agen tepercaya."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Memungkinkan aplikasi meluncurkan aktivitas yang mengubah perilaku agen tepercaya."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Ikat ke layanan agen kepercayaan"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Mengizinkan aplikasi mengikat ke layanan agen kepercayaan."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Berinteraksi dengan sistem pemulihan dan pembaruan"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dihapus"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kantor <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Gunakan lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app mengunci layar dalam satu aplikasi tunggal.\n\nUntuk keluar, tekan lama tombol aplikasi terbaru $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"TIDAK"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"MULAI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Mulai Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Keluar dari Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 3fd984b..81d6290 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modalità aereo attiva"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modalità aereo non attiva"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Impostazioni"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Blocca ora"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Consente l\'associazione di uno sfondo all\'interfaccia principale. Non dovrebbe mai essere necessaria per le normali applicazioni."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"collegamento a un servizio di interazione vocale"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Consente al titolare di collegarsi all\'interfaccia di primo livello di un servizio di interazione vocale. Non dovrebbe essere mai necessaria per le normali app."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestione delle frasi chiave vocali"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Consente al titolare di gestire le frasi chiave per il rilevamento hotword vocale. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"collega a un display remoto"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Consente al titolare di collegarsi all\'interfaccia di primo livello di un display remoto. Non dovrebbe essere mai necessaria per le normali applicazioni."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"associazione a un servizio widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Consente all\'app di modificare i parametri di calibrazione del touch screen. Questa opzione non deve essere utilizzata per le app normali."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"accesso a certificati DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Consente a un\'app di fornire e utilizzare ceritificati DRM. Questa opzione non deve essere utilizzata per app normali."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Ricevi trasmissioni del trasferimento."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Consente di ricevere informazioni sullo stato del trasferimento."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Impostazione regole password"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controlla la lunghezza e i caratteri ammessi nelle password di sblocco dello schermo."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Controllo tentativi di sblocco dello schermo"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Consente all\'applicazione di modificare la cronologia o i segnalibri del Browser memorizzati sul telefono. Ciò potrebbe consentire all\'applicazione di cancellare o modificare i dati del Browser. Nota. È possibile che questa autorizzazione non sia applicabile da browser di terze parti o altre applicazioni con funzionalità di navigazione web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"impostazione allarme"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Consente all\'applicazione di impostare un allarme in un\'applicazione sveglia installata. È possibile che alcune applicazioni sveglia non possano implementare questa funzione."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"rimuovi messaggi vocali"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Consente all\'app di rimuovere messaggi dalla segreteria."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"aggiunta di un messaggio vocale"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Consente all\'applicazione di aggiungere messaggi alla casella della segreteria."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accesso alla segreteria"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Consente all\'app di accedere alla segreteria."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifica delle autorizzazioni di localizzazione geografica del browser"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Consente all\'applicazione di modificare le autorizzazioni di geolocalizzazione del Browser. Le applicazioni dannose potrebbero farne uso per consentire l\'invio di informazioni sulla posizione a siti web arbitrari."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifica dei pacchetti"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Consente a un\'applicazione di rilevare le modifiche nello stato trust."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Indica un trust agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Consente a un\'applicazione di indicare un trust agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Avvio del menu di impostazioni del trust agent."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Consente a un\'applicazione di avviare un\'attività che modifica il comportamento del trust agent."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Associazione a un servizio trust agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Consente a un\'applicazione di associarsi a un servizio trust agent."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interazione con il sistema di ripristino e aggiornamento"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Seleziona anno"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Elemento selezionato: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminato"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> lavoro"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Utilizzare Lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"La funzione Lock-to-app consente di bloccare la visualizzazione in un\'unica app.\n\nPer uscire, premi e tieni premuto il pulsante delle app recenti $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"AVVIA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Avvia Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Esci da Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 1f2b75c..d8c6675 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"מצב טיסה מופעל"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"מצב טיסה כבוי"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"הגדרות"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"נעל עכשיו"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"מאפשר למשתמש לבצע איגוד לממשק הרמה העליונה של טפט. הרשאה זו לעולם אינה נחוצה לאפליקציות רגילים."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"איגוד לשירות אינטראקציה קולית"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"מאפשרת לבעלים לאגד לממשק ברמה העליונה של שירות אינטראקציה קולית. לעולם לא אמורה להיות נחוצה עבור אפליקציות רגילות."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ניהול של ביטויי מפתח קוליים"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"מאפשרת לבעלים לנהל את ביטויי המפתח עבור זיהוי של מילת הפעלה. לעולם לא אמורה להיות נחוצה עבור אפליקציות רגילות."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"איגוד לצג מרוחק"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"הרשאה זו מאפשרת למשתמש לבצע איגוד לממשק הרמה העליונה של צג רחוק. לעולם אינה אמורה להיות נחוצה לאפליקציות רגילות."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"‏הכפפה לשירות Widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"מאפשרת לאפליקציה לשנות את פרמטרי הכיול של מסך המגע. לעולם לא אמורה להיות נחוצה לאפליקציות רגילות."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"‏גישה אל אישורי DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"‏מאפשרת לאפליקציה לנהל תצורה של אישורי DRM ולהשתמש בהם. לעולם לא אמורה להיות נחוצה עבור אפליקציה רגילה."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"קבל שידורים על העברת שליטה."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"מאפשר קבלה של מידע על סטטוס העברת שליטה."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"הגדר כללי סיסמה"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"שלוט באורך ובתווים המותרים בסיסמאות לביטול נעילת מסך."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"עקוב אחר ניסיונות לביטול נעילת מסך"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"‏מאפשר לאפליקציה לשנות את ההיסטוריה או ה-Bookmarks של הדפדפן המאוחסנים בטלפון. הדבר עשוי לאפשר לאפליקציה למחוק או לשנות נתוני דפדפן. שים לב: אישור זה אינו ניתן לאכיפה על ידי דפדפני צד שלישי או אפליקציות אחרות בעלות יכולות גלישה באינטרנט."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"הגדרת התראה"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"מאפשר לאפליקציה להגדיר התראה באפליקציה מותקנת של שעון מעורר. אפליקציות מסוימות של שעון מעורר אינן מיישמות תכונה זו."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"הסרה של הודעות דואר קולי"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"מאפשרת לאפליקציה להסיר הודעות מתיבת הדואר הנכנס של דואר קולי."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"הוסף דואר קולי"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"מאפשר לאפליקציה להוסיף הודעות לתיבת הדואר הקולי."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"קריאת כל הדואר הקולי"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"מאפשרת לאפליקציה לקרוא את כל הודעות הדואר הקולי שלך."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"שינוי הרשאות המיקום הגיאוגרפי של הדפדפן"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"מאפשר לאפליקציה לשנות את הרשאות המיקום הגיאוגרפי של הדפדפן. אפליקציות זדוניות עלולות להשתמש בכך כדי לאפשר משלוח של פרטי מיקום לאתרים זדוניים אחרים."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"אימות חבילות"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"מאפשר לאפליקציה לחפש שינויים במצב אמון."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"ציון סוכן אמון."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"מאפשר לאפליקציה לספק סוכן אמון."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"הפעלת תפריט ההגדרות של סוכן האמון."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"מאפשר לאפליקציה להתחיל פעילות המשנה את ההתנהגות של סוכן האמון."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"איגוד אל שירות סוכן אמון"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"מאפשר לאפליקציה לאגוד אל שירות סוכן אמון."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"אינטראקציה עם מערכת שחזור ועדכונים"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"בחר שנה"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> נבחר"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> נמחק"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"עבודה <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"האם להשתמש ב\'נעל באפליקציה\'?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"\'נעל באפליקציה\' נועל את המסך באפליקציה יחידה.\n\nכדי לצאת, לחץ על לחצן האפליקציות האחרונות $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"לא"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"הפעל"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"הפעל את \'נעל באפליקציה\'"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"צא מ\'נעל באפליקציה\'"</string>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 6aedff7..1ad0573 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"機内モードON"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"機内モードOFF"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"設定"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"壁紙のトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"音声対話サービスへのバインド"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"音声対話サービスのトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"音声キーフレーズの管理"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"音声注目ワードの認識用キーフレーズを管理することを所有者に許可します。通常のアプリでは不要です。"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"リモートディスプレイへのバインド"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"リモートディスプレイのトップレベルインターフェースにバインドすることを所有者に許可します。通常のアプリでは不要です。"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ウィジェットサービスにバインド"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"タッチスクリーンの調整パラメータの変更をアプリに許可します。通常のアプリでは必要ありません。"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM証明書へのアクセス権"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"DRM証明書のプロビジョニングと使用をアプリに許可します。通常のアプリでは不要です。"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"パスワードルールの設定"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"画面ロック解除パスワードの長さと使用できる文字を制御します。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"画面ロック解除試行の監視"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"携帯端末に保存されているブラウザの履歴やブックマークの変更をアプリに許可します。これにより、アプリがブラウザデータを消去または変更できるようになる可能性があります。注: この許可は、サードパーティブラウザまたはウェブブラウジング機能を備えたその他のアプリでは適用されない場合があります。"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"アラームの設定"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"インストール済みアラームアプリのアラームを設定することをアプリに許可します。この機能が実装されていないアラームアプリもあります。"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ボイスメールの追加"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ボイスメール受信トレイにメッセージを追加することをアプリに許可します。"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"すべてのボイスメールの読み取り"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"すべてのボイスメールの読み取りをアプリに許可します。"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ブラウザの現在地情報に対する権限の変更"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ブラウザの現在地情報に対する権限の変更をアプリに許可します。この許可を悪意のあるアプリに利用されると、任意のウェブサイトに現在地情報が送信される恐れがあります。"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"パッケージのベリファイ"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"信頼状態の変更をリッスンすることをアプリに許可します。"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"信頼できるエージェントの提供"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"信頼できるエージェントの提供をアプリに許可します。"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"信頼できるエージェントの設定メニューの起動"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"信頼できるエージェントの動作を変更するアクティビティを開始することをアプリに許可します。"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"信頼できるエージェントサービスへのバインド"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"信頼できるエージェントサービスにバインドすることをアプリに許可します。"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"アップデートと回復システムへのアクセス"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"年を選択"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>を選択しました"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g>を削除しました"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"仕事の<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Lock-to-appの使用"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-appでは1つのアプリの表示をロックします。\n\n終了するには[最近使ったアプリ]ボタン($)を押し続けます"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"開始しない"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"開始する"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Lock-to-appを開始"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Lock-to-appを終了"</string>
 </resources>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index 01824b2..bd6dea96 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"თვითმფრინავის რეჟიმი ჩართულია."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"თვითმფრინავის რეჟიმი გამორთულია."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"პარამეტრები"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"მფლობელს შეეძლება ფონის ზედა დონის ინტერფეისთან დაკავშირება. არასდროს გამოიყენება ჩვეულებრივ აპებში."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ხმის ინტერაქტორთან შეკავშირება"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"მფლობელს შეეძლება შეკავშირდეს ხმის ინტერაქციის სერვისების ზედა დონის ინტერფეისთან. ჩვეულებრივ აპს ეს წესით არასოდეს უნდა დასჭირდეს."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ხმოვანი საიდუმლო ფრაზების მართვა"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"საშუალებას აძლევს მფლობელს მართოს საიდუმლო ფრაზები ხმოვანი ჯადოსნური სიტყვის ამოცნობისათვის. ეს ჩვეულებრივ აპებს არ უნდა დასჭირდეს."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"დისტანციურ მონიტორზე მიბმა"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"მფლობელს შეეძლება მიებას დისტანციურ მონიტორის ზედა დონის ინტერფეისს. ჩვეულებრივ აპს ეს წესით არასოდეს უნდა დაჭირდეს."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ვიჯეტ სერვისთან დაკავშირება"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"საშუალებას აძლევს აპს შეცვალოს სენსორული ეკრანის კალიბრაციის პარამეტრები. ჩვეულებრივ აპებს წესით არ უნდა დაჭირდეს."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM სერთიფიკატებზე წვდომა"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"საშუალებას აძლევს აპლიკაციას დანერგოს და გამოიყენოს DRM სერთიფიკატები. ეს უფლება ჩვეულებრივ აპებს არ ჭირდება."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"პაროლის წესების დაყენება"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"გააკონტროლეთ ეკრანის განბლოკვის პაროლში დაშვებული სიმბოლოები და მისი სიგრძე."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"ეკრანის განბლოკვის მცდელობების გაკონტროლება"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"აპს შეეძლება, შეცვალოს ბრაუზერის ისტორია და თქვენ ტელეფონში შენახული სანიშნეები. ამან შეიძლება უფლება მისცეს აპს, წაშალოს ან შეცვალოს ბრაუზერის მონაცემები. შენიშვნა: ეს ნებართვა არ შეიძლება შესრულდეს მესამე მხარის ბრაუზერების ან ვებ დათვალიერების შესაძლებლობის მქონე სხვა აპლიკაციების მიერ."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"მაღვიძარას დაყენება"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"აპს შეეძლება მაღვიძარას დაყენება დაინსტალირებული მაღვიძარას აპლიკაციაში. ამ ფუნქციას მაღვიძარას ზოგიერთი აპი არ იყენებს."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ხმოვანი ფოსტის დამატება"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"აპს შეეძლება დაამატოს შეტყობინებები თქვენი ხმოვანი ფოსტის შემოსულებში."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"მთელი ხმოვანი ფოსტის წაკითხვა"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"აპს ეძლევა მთელი თქვენი ხმოვანი ფოსტების წაკითხვის უფლება."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ბრაუზერის გეოლოკაციის უფლებების შეცვლა"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"აპს შეეძლება ბრაუზერის გეოლოკაციის უფლებების შეცვლა. მავნე აპებმა ეს შესაძლოა გამოიყენონ  ნებისმიერი ვებსაიტისთვის მდებარეობის შესახებ ინფორმაციის გასაგზავნად."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"პაკეტების გადამოწმება"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"საშუალებას აძლევს აპლიკაციას მოუსმინოს ცვლილებებს სანდო მდგომარეობაში."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"სანდო აგენტის წარმოდგენა."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"საშუალებას აძლევს აპლიკაციას წარმოადგინოს სანდო აგენტი."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ნდობის აგენტის პარამეტრების მენიუს გამოძახება."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"საშუალებას აძლევს აპლიკაციას გამოიძახოს აქტივობა, რაც ნდობის აგენტის ქცევას ცვლის."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"სანდო აგენტის სერვისზე მიმაგრება."</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"საშუალებას აძლევს აპლიკაციას მიემაგროს სანდო აგენტის სერვისს."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"განახლებასთან და აღდგენის სისტემასთან ინტერაქცია"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"აირჩიეთ წელი"</string>
     <string name="item_is_selected" msgid="949687401682476608">"არჩეულია <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> წაიშალა"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"სამსახური <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"გსურთ „აპში ჩაკეტვის“ გამოყენება?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"„აპში ჩაკეტვა“ კეტავს ეკრანს ერთ აპში.\n\nგასასვლელად დააჭირეთ და არ აუშვათ ღილაკს „ბოლო აპები“ $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"არა"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"დაწყება"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"„აპში ჩაკეტვის“ დაწყება"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"„აპში ჩაკეტვიდან“ გასვლა"</string>
 </resources>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index c2478e4..f54b36e 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"បាន​បើក​របៀប​ពេល​ជិះ​យន្ត​ហោះ"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"បាន​បិទ​របៀបពេលជិះ​យន្តហោះ"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"ការ​កំណត់"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ឲ្យ​ម្ចាស់​ចង​ចំណុចប្រទាក់​កម្រិត​កំពូល​នៃ​ផ្ទាំង​រូបភាព។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"ភ្ជាប់​ទៅ​​អ្នក​សហការ​សំឡេង"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"អនុញ្ញាត​ឲ្យ​ម្ចាស់​ភ្ជាប់​ទៅ​ចំណុច​ប្រទាក់​កម្រិត​កំពូល​​របស់​សេវាកម្ម​អន្តរកម្ម​សំឡេង។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"គ្រប់គ្រង​ឃ្លា​សំឡេង"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"អនុញ្ញាត​ឲ្យ​ម្ចាស់​គ្រប់គ្រង​ឃ្លា​​សម្រាប់​​ការ​រក​ឃើញ​​​ពាក្យ​​ជា​សំឡេង។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ភ្ជាប់​ទៅ​ការ​បង្ហាញ​ពី​ចម្ងាយ"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"អនុញ្ញាត​ឲ្យ​ម្ចាស់​ភ្ជាប់​​ទៅ​ចំណុច​ប្រទាក់​កម្រិត​កំពូល​នៃ​ការ​បង្ហាញ​ពី​ចម្ងាយ។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ចង​សេវា​កម្ម​ធាតុ​ក្រាហ្វិក"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"ឲ្យ​​កម្មវិធី​កែ​ប៉ារ៉ាម៉ែត្រ​កែ​ចំណុច​​នៃ​ការ​ប៉ះ​អេក្រង់។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"ចូល​មើល​វិញ្ញាបនបត្រ DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"ឲ្យ​កម្មវិធី​ផ្ដល់ និង​ប្រើ​វិញ្ញាបនបត្រ DRM ។ មិន​គួរ​ចាំបាច់​សម្រាប់​កម្មវិធី​ធម្មតា​ទេ។"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"កំណត់​ក្បួន​ពាក្យ​សម្ងាត់"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"ពិនិត្យ​ប្រវែង និង​តួអក្សរ​ដែល​បាន​អនុញ្ញាត​ក្នុង​ពាក្យ​សម្ងាត់​ចាក់​សោ​អេក្រង់។"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"ពិនិត្យ​ការ​ព្យាយាម​ដោះ​សោ​អេក្រង់"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"ឲ្យ​កម្មវិធី​កែ​ប្រវត្តិ ឬ​ចំណាំ​របស់​កម្មវិធី​អ៊ីនធឺណិត​ដែល​បាន​រក្សាទុក​ក្នុង​ទូរស័ព្ទ​​របស់​អ្នក។ កម្មវិធី​ព្យាបាទ​អាច​ប្រើ​វា​ដើម្បី​លុប ឬ​កែ​ទិន្នន័យ​នៃ​កម្មវិធី​អ៊ីនធឺណិត​របស់​អ្នក។ ចំណាំ៖ សិទ្ធិ​នេះ​អាច​ត្រូវ​បាន​បង្ខំ​ដោយ​កម្មវិធី​អ៊ីនធឺណិត​​ភាគី​ទីបី​ ឬ​​កម្មវិធី​ផ្សេង​ដែល​មាន​សមត្ថភាព​រុករក​បណ្ដាញ។ស"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"កំណត់​សំឡេង​រោទ៍"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"ឲ្យ​កម្មវិធី​កំណត់​​សំឡេង​រោទ៍​ក្នុង​កម្មវិធី​នាឡិកា​រោទ៍​បាន​ដំឡើង។​ កម្មវិធី​នាឡិកា​រោទ៍​មួយ​ចំនួន​អាច​មិន​អនុវត្ត​លក្ខណៈ​នេះ។"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"បន្ថែម​សារ​ជា​សំឡេង"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ឲ្យ​កម្មវិធី​បន្ថែម​សារ​ទៅ​ប្រអប់​ទទួល​សារ​ជា​សំឡេង​របស់​អ្នក។"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"អាន​សារ​ជា​សំឡេង​ទាំងអស់"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ឲ្យ​កម្មវិធី​អាន​​សារ​ជា​សំឡេង​របស់​អ្នក​ទាំងអស់។"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"កែ​សិទ្ធិ​ទីតាំង​ភូមិសាស្ត្រ​របស់​​កម្មវិធី​អ៊ីនធឺណិត"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ឲ្យ​កម្មវិធី​កែ​​សិទ្ធិ​ទី​តាំង​ភូមិសាស្ត្រ​របស់​កម្មវិធី​អ៊ីនធឺណិត។ កម្មវិធី​ព្យាបាទ​អាច​ប្រើ​វា ដើម្បី​ឲ្យ​ផ្ញើ​ព័ត៌មាន​ទីតាំង​ទៅ​តំបន់បណ្ដាញ​ដោយ​បំពាន។"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ផ្ទៀងផ្ទាត់​កញ្ចប់"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"ឲ្យ​កម្មវិធី​ស្ដាប់​ការ​ផ្លាស់ប្ដូរ​ក្នុង​ស្ថានភាព​ដែល​​ទុកចិត្ត។"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"ផ្ដល់​ភ្នាក់ងារ​ដែល​ទុកចិត្ត។"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ឲ្យ​កម្មវិធី​ផ្ដល់​ភ្នាក់ងារ​ដែល​ទុកចិត្ត។"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ចាប់ផ្ដើម​ម៉ឺនុយ​ការ​កំណត់​ភ្នាក់ងារ​ជឿ​ទុកចិត្ត។"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"អនុញ្ញាត​ឲ្យ​​កម្មវិធី​ចាប់ផ្ដើម​សកម្ម​​ដែល​ផ្លាស់​ប្ដូរ​ឥរិយា​បថ​​ភ្នាក់ងារ​ជឿ​ទុកចិត្ត​​។"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ភ្ជាប់​ទៅ​សេវាកម្ម​ភ្នាក់ងារ​ដែល​ទុកចិត្ត"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"ឲ្យ​កម្មវិធី​ភ្ជាប់​សេវាកម្ម​ភ្នាក់ងារ​ដែល​ទុក​ចិត្ត។"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"អន្តរកម្ម​ជា​មួយ​បច្ចុប្បន្នភាព និង​ប្រព័ន្ធ​សង្គ្រោះ"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"ជ្រើស​ឆ្នាំ"</string>
     <string name="item_is_selected" msgid="949687401682476608">"បាន​ជ្រើស <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"បាន​លុប <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"កន្លែង​ធ្វើការ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"ប្រើ​ការ​ចាក់សោ​កម្មវិធី?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"ចាក់សោ​កម្មវិធី​គឺ​ចាក់​សោ​ការ​បង្ហាញ​​ក្នុង​កម្មវិធី​តែ​មួយ។\n\nដើម្បី​ចាកចេញ ចុច​ និង​សង្កត់​ប៊ូតុង​កម្មវិធី​ថ្មីៗ $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"ទេ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ចាប់ផ្ដើម"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"ចាប់ផ្ដើម​ការ​ចាក់​សោ​កម្មវិធី"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"ចេញ​ពី​ការ​ចាក់​សោ​កម្មវិធី"</string>
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index db02343..39ac525 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"비행기 모드 사용중"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"비행기 모드 사용중이 아님"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"설정"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"권한을 가진 프로그램이 배경화면에 대한 최상위 인터페이스를 사용하도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"음성 상호작용 서비스 사용"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"권한을 가진 프로그램이 음성 상호작용 서비스의 최상위 인터페이스를 사용하도록 합니다. 일반 앱에는 필요하지 않습니다."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"음성 핵심문구 관리"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"권한을 가진 프로그램이 음성 핫워드 감지용 핵심문구를 관리할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"원격 디스플레이에 연결"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"권한을 가진 프로그램이 원격 디스플레이에 대한 최상위 인터페이스를 사용하도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"위젯 서비스와 연결"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"앱이 터치 스크린의 보정 매개변수를 수정할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM 인증서에 액세스"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"애플리케이션이 DRM 인증서를 프로비저닝하고 사용하도록 허용합니다. 일반 앱에서는 필요하지 않습니다."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"비밀번호 규칙 설정"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"화면 잠금해제 비밀번호에 허용되는 길이 및 문자 수를 제어합니다."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"화면 잠금해제 시도 모니터링"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"앱이 휴대전화에 저장된 브라우저 기록 또는 북마크를 수정할 수 있도록 허용합니다. 이 경우 앱이 브라우저 데이터를 삭제 또는 수정할 수 있습니다. 참고: 이 권한은 타사 브라우저 또는 브라우저 기능을 가진 기타 애플리케이션에 적용되지 않습니다."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"알람 설정"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"앱이 설치된 알람 시계 앱에서 알람을 설정할 수 있도록 허용합니다. 일부 알람 시계 앱에는 이 기능이 구현되지 않을 수 있습니다."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"음성사서함 추가"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"앱이 음성사서함에 메시지를 추가할 수 있도록 허용합니다."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"모든 음성사서함 읽기"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"앱이 모든 음성사서함을 읽을 수 있도록 허용합니다."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"브라우저 위치 정보 권한 수정"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"앱이 브라우저의 위치 정보 권한을 수정할 수 있도록 허용합니다. 이 경우 악성 앱이 이 기능을 이용하여 임의의 웹사이트에 위치 정보를 보낼 수 있습니다."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"패키지 확인"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"애플리케이션이 Trust 상태에서의 변경사항을 수신할 수 있도록 허용합니다."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Trust Agent 제공"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"애플리케이션이 Trust Agent를 제공할 수 있도록 허용합니다."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Trust Agent 설정 메뉴를 실행합니다."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"애플리케이션에서 Trust Agent의 동작을 변경하는 활동을 실행하도록 허용합니다."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Trust Agent 서비스에 연결"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"애플리케이션이 Trust Agent 서비스에 바인딩할 수 있도록 허용합니다."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"업데이트 및 복구 시스템과 상호작용"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"연도 선택"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>이(가) 선택됨"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 삭제됨"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"업무용 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"앱 잠금 기능을 사용하시겠습니까?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"앱 잠금 기능은 단일 앱의 화면을 잠급니다.\n\n종료하려면 최근 앱 버튼 $를 길게 누릅니다."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"아니요"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"시작"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"앱 잠금 기능 시작"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"앱 잠금 기능 종료"</string>
 </resources>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 0e7f0c3..0561613 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"ເປີດໂໝດຢູ່ໃນຍົນແລ້ວ"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"ປິດໂໝດໃນຍົນແລ້ວ"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"​ການ​ຕັ້ງ​ຄ່າ"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"ລັອກ​ດຽວ​ນີ້"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"ອະນຸຍາດໃຫ້ຜູ່ໃຊ້ເຊື່ອມໂຍງກັບສ່ວນຕິດຕໍ່ລະດັບສູງສຸດ ຂອງພາບພື້ນຫຼັງໃດນຶ່ງ. ແອັບຯທຳມະດາບໍ່ຄວນຈຳເປັນຕ້ອງໃຊ້."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"​ເຊື່ອມ​ໂຍງ​ກັບ​ຕົວ​ຕິດ​ຕໍ່​ດ້ວຍ​ສຽງ"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"ອະ​ນຸ​ຍາດ​ໃຫ້​ເຈົ້າ​ຂອງ​ເຊື່ອມ​ໂຍງ​ສ່ວນ​ຕິດ​ຕໍ່​ລະ​ດັບ​ສູງ​ສຸດຂອງ​ບໍ​ລິ​ການ​ການ​ຕິດ​ຕໍ່​ດ້ວຍ​ສຽງ."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"ຈັດການລະຫັດສຽງ"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"ອະນຸຍາດ​ໃຫ້​ຈັດການລະຫັດ​ສຳລັບ​ການ​ກວດ​ພົບ​ຄຳ​ສັ່ງ​ສຽງ. ແອັບຯ​ທົ່ວໄປ​ບໍ່​ຄວນ​ຕ້ອງ​ໃຊ້."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ຜູກກັນເພື່ອສະແດງຜົນທາງໄກ."</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"ອະນຸຍາດໃຫ້ຜູ່ຖືຜູກກັບສ່ວນຕິດຕໍ່ລະດັບສູງສຸດ ຂອງການສະແດງຜົນທາງໄກ. ບໍ່ຈຳເປັນສຳລັບແອັບຯທົ່ວໄປ."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ເຊື່ອມໂຍງໄປຫາບໍລິການວິດເຈັດ"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບຯ​ແກ້​ໄຂ​ຄ່າ​ການວັດ​ແທ້​ໜ້າ​ຈ​ໍ​ສຳ​ຜັດ. ​ແອັບຯ​ທຳ​ມະ​ດາບໍ່​ຄວນ​ໃຊ້."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"ເຂົ້າ​ເຖິງ​ໃບຮັບຮອງ DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"ອະນຸຍາດ​ໃຫ້​ແອັບພລິເຄຊັນ​ຈັດຫາ ແລະ​ນຳໃຊ້​ໃບຮັບຮອງ DRM. ແອັບຯ​ທຳມະດາ​ບໍ່​ຄວນ​ຕ້ອງ​ການ​ໃຊ້."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"ຮັບ​ການ​ກະຈາຍ​ຂໍ້ມູນ​ການ​ໂອນຍ້າຍ​ການ​ສົ່ງຕໍ່."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"ອະນຸຍາດ​ການ​ຮັບ​ຂໍ້ມູນ​ສະຖານະ​ການ​ສົ່ງ​ຕໍ່."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"ຕັ້ງຄ່າກົດຂອງລະຫັດຜ່ານ"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"ຄວບຄຸມຄວາມຍາວຂອງໂຕອັກສອນທີ່ສາມາດໃຊ້ກັບລະຫັດປົດລັອກໜ້າຈໍ"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"ຕິດຕາມການພະຍາຍາມປົດລັອກໜ້າຈໍ"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"ອະນຸຍາດໃຫ້ແອັບຯ ແກ້ໄຂປະຫວັດໂປຣແກຣມທ່ອງເວັບ ຫຼືບຸກມາກທີ່ເກັບໄວ້ໃນໂທລະສັບຂອງທ່ານ. ນີ້ອາດອະນຸຍາດໃຫ້ແອັບຯລຶບ ຫຼືແກ້ໄຂຂໍ້ມູນໂປຣແກຣມທ່ອງເວັບ. ໝາຍເຫດ: ການກຳນົດສິດນີ້ ອາດບໍ່ໄດ້ຖືກບັງຄັບໃຊ້ໃນໂປຣແກຣມທ່ອງເວັບພາກສ່ວນທີສາມ ຫຼືແອັບພລິເຄຊັນອື່ນທີ່ມີຄວາມສາມາດທ່ອງເວັບ."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ຕັ້ງການແຈ້ງເຕືອນ"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"ອະນຸຍາດໃຫ້ແອັບຯຕັ້ງໂມງປຸກໃນແອັບຯໂມງປຸກທີ່ຕິດຕັ້ງໄວ້. ບາງແອັບຯໂມງປຸກອາດບໍ່ມີຄຸນສົມບັດແບບນີ້ເທື່ອ."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"ລຶບ​ຂໍ້ຄວາມ​ສຽງ​ອອກ"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"ອະນຸຍາດ​ໃຫ້​ແອັບຯ​ລຶບ​ຂໍ້​ຄວາມ​ຈາກ​ອິນບັອກ​ຂໍ້​ຄວາມ​ສຽງ​ຂອງ​ທ່ານ​ໄດ້."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ເພີ່ມຂໍ້ຄວາມສຽງ"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"ອະນຸຍາດໃຫ້ແອັບຯ ສາມາດເພີ່ມຂໍ້ຄວາມໃສ່ອິນບັອກຂໍ້ຄວາມສຽງຂອງທ່ານໄດ້."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ອ່ານ​ຂໍ້​ຄວາມ​ສຽງ​ທັງ​ໝົດ"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"​ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບຯ​ອ່ານ​ຂໍ້​ຄວາມ​ສຽງ​ທັງ​ໝົດ​ຂອງ​ທ່ານ."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ແກ້ໄຂສິດທາງສະຖານທີ່ພູມສາດຂອງໂປຣແກຣມທ່ອງເວັບ"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂ ການອະນຸຍາດຕຳແໜ່ງທາງພູມສາດ ຂອງໂປຣແກຣມທ່ອງເວັບ. ແອັບຯທີ່ເປັນອັນຕະລາຍອາດໃຊ້ຄຸນສົມບັດນີ້ ເພື່ອສົ່ງຂໍ້ມູນສະຖານທີ່ໄປໃຫ້ເວັບໄຊຕ່າງໆໄດ້."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ຢັ້ງຢືນແພັກເກດ"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບ​ພ​ລິ​ເຄ​ຊັນ​ຕິດ​ຕາມ​​ການ​ປ່ຽນ​ແປງ​ໃນ​ສະ​ຖາ​ນະ​ການ​ເຊື່ອ​ຖື."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"​ລະ​ບຸ​ເອ​ເຈນ​ທີ່​ເຊື່ອ​ຖື​ໄດ້."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບ​ພ​ລິ​ເຄ​ຊັນ​ລະ​ບຸ​ເອ​ເຈນ​ທີ່​ເຊື່ອ​ຖື​ໄດ້."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"ເປີດ​ເມ​ນູການ​ຕັ້ງ​ຄ່າ​ເອ​ເຈັນ​ທີ່​ເຊື່ອ​ຖື​ໄດ້​."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"​ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບ​ພລິ​ເຄ​ຊັນ​ເປີດ​ການ​ເຄື່ອນ​ໄຫວ​ທີ່​ປ່ຽນ​ແປງ​ພຶດ​ຕິ​ກຳ​ຂອງ​ເອ​ເຈັນ​ທີ່​ເຊື່ອ​ຖື​ໄດ້."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"​ເຊື່ອມ​ໂຍງ​ຫາ​ບໍ​ລິ​ການ​ຕົວ​ແທນ​ການ​ເຊື່ອ​ຖື"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"​ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບ​ພ​ລິ​ເຄ​ຊັນເຊື່ອມ​ໂຍງ​ກັບ​ບໍ​ລິ​ການ​ຕົວ​ແທນ​ທີ່​ເຊື່ອ​ຖື​ໄດ້."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"ຕິດຕໍ່ກັບລະບົບອັບເດດ ແລະລະບົບກູ້ຂໍ້ມູນ."</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"ເລືອກ​ປີ"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ຖືກເລືອກແລ້ວ"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ຖືກລຶບແລ້ວ"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"​ບ່ອນ​ເຮັດ​ວຽກ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"​ນຳ​ໃຊ້ lock-to-app ບໍ່?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app ​​ຈະລັອກ​ໜ້າ​ຈໍ​ໄວ້​​ຢູ່​ກັບແອັບ​ດຽວ.\n\n​ເພື່​ອ​ອອກ​ຈາກ​ມັນ ກະ​ລຸ​ນາ​ກົດ​ປຸ​່ມ​ແອັບທີ່​ຫາ​ກໍ​ໃຊ້​ຄ້າງ​ໄວ້ $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"ບໍ່"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"​ເລີ່ມ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"​ເລີ່ມ​ຕົ້ນ Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"​ອອກ​ຈາກ Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index e9b1247..3988cee 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"ĮJUNGTAS lėktuvo režimas"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"lėktuvo režimas IŠJUNGTAS"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Nustatymai"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Leidžiama savininką susaistyti su aukščiausio lygio darbalaukio fono sąsaja. Įprastoms programoms to neturėtų prireikti."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"susaistyti su sąveikos balsu priemone"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Turėtojui leidžiama susaistyti programą su sąveikos balsu paslaugos aukščiausio lygio sąsaja. Įprastoms programoms to niekada neturėtų prireikti."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"tvarkyti pagrindines balso frazes"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Savininkui leidžiama tvarkyti pagrindines aktyvinamiesiems žodžiams aptikti skirtas frazes. To niekada neturėtų prireikti naudojant įprastas programas."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"susisaistyti su nuotoliniu ekranu"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Leidžiama savininkui susisaistyti su aukščiausiojo lygio nuotolinio ekrano sąsaja. Įprastoms programoms to neturėtų prireikti."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"susaistyti su valdiklio paslauga"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Leidžiama programai keisti jutiklinio ekrano kalibravimo parametrus. Neturėtų prireikti naudojant įprastas programas."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"gali pasiekti DRM sertifikatus"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Programai leidžiama pasiekti ir naudoti DRM sertifikatus. Neturėtų prireikti naudojant įprastas programas."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Nustatyti slaptažodžio taisykles"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Valdyti leidžiamą ekrano atrakinimo slaptažodžių ilgį ir leidžiamus naudoti simbolius."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Stebėti bandymus atrakinti ekraną"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Leidžiama programai keisti naršyklės istoriją ar žymes, išsaugotas telefone. Dėl to programai gali būti leidžiama ištrinti ar keisti naršyklės duomenis. Pastaba: šis leidimas nesuteikiamas trečiosios šalies naršyklėms ar kitoms programoms, kuriomis galima naršyti žiniatinklį."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"nustatyti pavojaus signalą"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Leidžiama programai nustatyti signalą įdiegtoje žadintuvo programoje. Kai kuriose žadintuvo programose ši funkcija gali nebūti nevykdoma."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"pridėti balso pašto pranešimų"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Leidžia programai pridėti pranešimų prie jūsų balso pašto gautųjų."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"skaityti visus balso pašto pranešimus"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Programai leidžiama skaityti visus balso pašto pranešimus."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"keisti naršyklės geografinės vietos leidimus"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Leidžiama programai keisti naršyklės geografinės vietos leidimus. Kenkėjiškos programos gali tai naudoti, kad leistų siųsti vietos informaciją abejotinoms svetainėms."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"patikrinti paketus"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Programai leidžiama atsižvelgti į patikimos būsenos pakeitimus."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Teikti patikimos priemonės paslaugą."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Programai leidžiama teikti patikimos priemonės paslaugą."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Paleisti patikimo atstovo nustatymų meniu."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Leidžiama programai paleisti veiklą, keičiančią patikimo atstovo elgseną."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Susisaistyti su „trust agent“ paslauga"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Programai leidžiama susisaistyti su „trust agent“ paslauga."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Sąveikauti su naujiniu ir atkūrimo sistema"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Pasirinkite metus"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Pasirinkta: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Ištrinta: <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbo <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Naudoti programos užrakinimo funkciją?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Naudojant programos užrakinimo funkciją ekrane užrakinama viena programa.\n\nJei norite išeiti, paspauskite ir palaikykite paspaudę naujausių programų mygtuką $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ĮJUNGTI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Įjungti programos užrakinimo funkciją"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Išeiti iš programos užrakinimo funkcijos"</string>
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 8716114..d1686b2 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Lidojuma režīms ir IESLĒGTS."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Lidojuma režīms ir IZSLĒGTS."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Iestatījumi"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ļauj īpašniekam piesaistīt tapetes augstākā līmeņa lietotāja saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"Saistīšana ar balss mijiedarbības elementu"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ļauj īpašniekam izveidot savienojumu ar balss mijiedarbības pakalpojuma augšējā līmeņa saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"pārvaldīt balss atslēgas frāzes"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ļauj īpašniekam pārvaldīt atslēgas frāzes balss īsinājumvārdu identificēšanai. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"Saites izveide ar attālu displeju"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ļauj īpašniekam izveidot saiti ar attāla displeja augšējā līmeņa saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"saistīt ar logrīka pakalpojumu"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Ļauj lietotnei pārveidot skārienekrāna kalibrēšanas parametrus. Parastām lietotnēm šī atļauja nekad nav nepieciešama."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"Piekļuve digitālā satura tiesību pārvaldības sertifikātiem"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Ļauj lietojumprogrammai nodrošināt un izmantot digitālā satura tiesību pārvaldības sertifikātus. Parastām lietotnēm šī atļauja nekad nav nepieciešama."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Paroles kārtulu iestatīšana"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrolē ekrāna atbloķēšanas parolē atļautās rakstzīmes un garumu."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Ekrāna atbloķēšanas mēģinājumu pārraudzīšana"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Ļauj lietotnei mainīt tālrunī saglabāto pārlūkprogrammas vēsturi vai grāmatzīmes. Tas var ļaut lietotnei dzēst vai pārveidot pārlūkprogrammas datus. Piezīme: šo atļauju nevar piemērot trešo pušu pārlūkprogrammas vai citas lietojumprogrammas ar tīmekļa pārlūkošanas iespējām."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"iestatīt modinātāju"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Ļauj lietotnei iestatīt signālu instalētajā modinātājpulksteņa lietotnē. Dažās modinātājpulksteņu lietotnēs šo funkciju, iespējams, nevar ieviest."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"pievienot balss pastu"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ļauj lietotnei pievienot ziņojumus jūsu balss pasta iesūtnei."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lasīt visus balss pasta ziņojumus"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ļauj lietotnei lasīt visus jūsu balss pasta ziņojumus."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"pārveidot pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ļauj lietotnei modificēt pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas. Ļaunprātīgas lietotnes to var izmantot, lai atļautu atrašanās vietas informācijas sūtīšanu uz citām vietnēm."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakotņu verificēšana"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ļauj lietojumprogrammai klausīties uzticamības statusa izmaiņas."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Nodrošināt uzticamības pārbaudes programmu"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ļauj lietojumprogrammai nodrošināt uzticamības pārbaudes programmu."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Palaist uzticamības pārbaudes programmas iestatījumu izvēlni."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ļauj lietojumprogrammai palaist darbību, kas maina uzticamības pārbaudes programmas rīcību."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Izveidot savienojumu ar uzticamības pārbaudes pakalpojumu"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ļauj lietojumprogrammai izveidot savienojumu ar uzticamības pārbaudes pakalpojumu."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Mijiedarbošanās ar atjauninājumu un atkopšanas sistēmu"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Atlasiet gadu."</string>
     <string name="item_is_selected" msgid="949687401682476608">"Atlasīts: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> tika dzēsts."</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbā: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Vai izmantot bloķēšanu darbībai vienā lietotnē?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Konfigurācija \"Bloķēšana darbībai vienā lietotnē\" nobloķē ekrānu vienā lietotnē.\n\nLai izietu, nospiediet un turiet pogu “Pēdējās izmantotās lietotnes” $."</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NĒ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"SĀKT"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Sākt bloķēšanu darbībai vienā lietotnē"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Iziet no bloķēšanas darbībai vienā lietotnē"</string>
 </resources>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 36d54d0..d2d15c8 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Нислэгийн горим асав"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Нислэгийн горим унтарсан"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Тохиргоо"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Эзэмшигч нь ханын зурагны дээд-төвшиний интерфейстэй холбох боломжтой. Энгийн апп-уудад шаардлагагүй."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"дуугаар харьцагчтай холбох"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Эзэмшигчид дуугаар харьцах үйлчилгээний дээд-түвшний интерфейстэй холбох боломж олгоно. Энгийн апп-уудад хэзээ ч ашиглагдахгүй."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"дууны гол хэллэгүүдийг удирдах"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Эзэмшигчид дууны хотворд-г таних гол хэллэгүүдийг удирдах боломж олгоно. Энгийн апп-д хэзээ ч ашиглагдахгүй."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"алсын дэлгэцтэй холбогдох"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Эзэмшигчид алсын дэлгэц дэх дээд давхаргын интерфэйстэй холбогдох боломж олгоно. Энгийн апп-д шаардагдахгүй."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"виджет үйлчилгээтэй холбох"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Мэдрэгчтэй дэлгэцний калибрешн параметрийг өөрчлөхийг апп-д зөвшөөрнө. Энгийн апп-д шаардлагагүй."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"хандалтын DRM сертификат"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Аппликешнд DRM сертификатыг ашиглах болон нийлүүлэхийг зөвшөөрнө. Энгийн апп-уудад хэзээ ч ашиглагдахгүй."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Нууц үгний дүрмийг тохируулах"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Дэлгэц түгжих нууц үгэнд зөвшөөрөгдсөн тэмдэгт болон уртыг удирдах"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Дэлгэц тайлах оролдлогыг хянах"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Апп нь таны утсан дээр хадгалагдсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөх боломжтой. Энэ нь апп-д Хөтчийн датаг арилгах эсвэл өөрчлөх боломжийг олгоно. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадвартай аппликешнд ажиллахгүй байх боломжтой."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"сэрүүлэг тохируулах"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Апп нь суулгагдсан сэрүүлэгний апп дээр сэрүүлэг тохируулах боломжтой. Зарим сэрүүлэгний апп нь энэ функцийг дэмжихгүй байж болзошгүй."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"дуут шуудан нэмэх"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Таны дуут шуудангийн ирсэн мэйлд зурвас нэмэхийг апп-д зөвшөөрөх."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"бүх дуут шууданг унших"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Апп-д таны бүх дуут шууданг унших боломж олгоно."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Хөтчийн геобайршлын зөвшөөрлийг өөрчлөх"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын веб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"багцийг тулгах"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Итгэмжлэлд орж буй өөрчлөлтийг мэдэх боломжийг аппликешнд олгоно."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Итгэмжлэгдсэн төлөөлөгч нийлүүлэх"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Аппликешнд итгэмжлэгдсэн төлөөлөгч нийлүүлэх боломж олгоно."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Итгэмжлэгдсэн агентын тохиргоо цэсийг эхлүүлэх."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Аппликешнд итгэмжлэгдсэн агентын авирыг өөрчлөх боломжтой үйлдлийг эхлүүлэхийг зөвшөөрнө."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Итгэмжлэгдсэн төлөөлөгчийн үйлчилгээтэй холбогдох"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Аппликешнд итгэмжлэгдсэн төлөөлөгчтэй холбогдох боломж олгоно."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Шинэчлэлт болон сэргээх системтэй харилцах"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Жилийг сонгоно уу"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> сонгогдсон"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> устсан"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Ажлын <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Апп-дотор-түгжих-г ашиглах уу?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Апп-дотор-түгжих нь дэлгэцийг нэг апп дотор түгжинэ.\n\nГарахын тулд саяхны апп-ууд товчийг дараад барина уу $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"ҮГҮЙ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ЭХЛҮҮЛЭХ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Апп-дотор-түгжих-г эхлүүлэх"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Апп-дотор-түгжих-с гарах"</string>
 </resources>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index e9d1e80..6182e42 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Mod Pesawat DIHIDUPKAN"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Mod Pesawat DIMATIKAN"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Tetapan"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi kertas dinding. Tidak sekali-kali diperlukan untuk apl biasa."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"terikat kepada interaksi suara"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi perkhidmatan interaksi suara. Tidak sekali-kali diperlukan untuk apl biasa."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"urus frasa kunci suara"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Membenarkan pemegang mengurus frasa kunci untuk pengesahan sebutan laluan suara. Tidak sekali-kali diperlukan untuk apl biasa."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"terikat kepada paparan jauh"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Membenarkan pemegang terikat dengan antara muka peringkat tertinggi bagi paparan jauh. Tidak sekali-kali diperlukan untuk apl biasa."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"terikat kepada perkhidmatan widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Membenarkan apl mengubah suai parameter penentukuran skrin sentuh. Ini tidak sekali-kali diperlukan untuk apl biasa."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"akses sijil DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Membenarkan aplikasi memperuntuk dan menggunakan sijil DRM. Tidak sekali-kali diperlukan untuk apl biasa."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Tetapkan peraturan kata laluan"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Mengawal panjang dan aksara yang dibenarkan dalam kata laluan buka kunci skrin."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Memantau percubaan buka kunci skrin"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Membenarkan apl mengubah suai sejarah atau penanda halaman Penyemak Imbas yang tersimpan pada telefon anda. Ini boleh membenarkan apl untuk memadam atau mengubah suai data Penyemak Imbas. Nota: kebenaran ini tidak boleh dikuatkuasakan oleh penyemak imbas pihak ketiga atau aplikasi lain dengan keupayaan menyemak imbas web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"tetapkan penggera"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Membenarkan apl untuk menetapkan penggera dalam apl penggera jam yang dipasang. Sesetengah applikasi jam penggera tidak boleh melaksanakan ciri ini."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"tambah mel suara"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Membenarkan apl untuk menambahkan mesej pada peti masuk mel suara anda."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"baca semua mel suara"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Membenarkan apl membaca semua mel suara anda."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ubah suai kebenaran geolokasi Penyemak Imbas"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Membenarkan apl untuk mengubah suai kebenaran geolokasi Penyemak Imbas. Apl hasad boleh menggunakannya untuk membenarkan menghantar maklumat lokasi kepada laman web sembarangan."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"sahkan pakej"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Membenarkan aplikasi mendengar perubahan dalam keadaan amanah."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Sediakan ejen amanah."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Membenarkan aplikasi menyediakan ejen amanah."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lancarkan menu tetapan ejen amanah."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Membenarkan aplikasi melancarkan aktiviti yang mengubah tingkah laku ejen amanah."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Mengikat kepada perkhidmatan ejen amanah"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Membenarkan aplikasi terikat kepada perkhidmatan ejen amanah."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Berinteraksi dengan kemas kini dan sistem pemulihan"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dipadamkan"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kerja <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Gunakan kunci ke apl?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Kunci ke apl mengunci paparan dalam apl tunggal.\n\nUntuk keluar tekan dan tahan butang apl terbaru $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"TIDAK"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"MULA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Mulakan Kunci ke apl"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Keluar kunci ke apl"</string>
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 8dace09..594d3cb 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flymodus er på"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flymodus er av"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Innstillinger"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Lar innehaveren binde det øverste nivået av grensesnittet til en bakgrunn. Skal aldri være nødvendig for vanlige apper."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"binde seg til en tjeneste for talehandlinger"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Gir innehaveren tillatelse til å binde til toppnivået av brukergrensesnittet for en tjeneste for talehandlinger. Dette skal ikke være nødvendig for vanlige apper."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"administrer nøkkelfraser for stemmebruk"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Tillater eieren å administrere nøkkelfraser for gjenkjennelse av talekommandoord. Skal aldri være nødvendig for normale apper."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"binde til ekstern skjerm"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Lar innehaveren binde seg til det øverste grensesnittnivået for ekstern skjerm. Skal aldri være nødvendig for vanlige apper."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"binde til modultjenste"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Lar appen endre kalibrasjonsparametrene for berøringsskjermen. Denne tillatelsen bør aldri være nødvendig for vanlige apper."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"tilgang til DRM-sertifikater"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Tillater at en app klargjøre og bruke DRM-sertifikater. Denne tillatelsen bør aldri være nødvendig for vanlige apper."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Angi passordregler"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontroller tillatt lengde og tillatte tegn i passord for opplåsing av skjerm."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Overvåk forsøk på opplåsing av skjerm"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Lar appen endre nettleserens logg eller bokmerker lagret på telefonen din. Dette kan føre til at appen sletter eller endrer nettleserdata. Vær oppmerksom på at denne tillatelsen kanskje ikke benyttes av tredjepartsnettlesere eller andre apper med mulighet for nettsurfing."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"stille alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Lar appen stille inn alarmen for en installert alarmklokke-app. Enkelte alarmklokke-apper implementerer kanskje ikke denne funksjonen."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"legge til talepost"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Lar appen legge til meldinger i talepostkassen din."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lese alle meldingene i talepostkassen"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Lar appen lese alle meldingene i talepostkassen din."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"endre nettleserens tillatelser for geoposisjonering"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Lar appen endre nettleserens tillatelser for geoposisjonering. Ondsinnede apper kan bruke dette for å tillate sending av posisjonsinformasjon til vilkårlige nettsteder."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekrefte pakker"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Gir appen tillatelse til å oppdage endringer i tillitsstatusen."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Angivelse av en pålitelig agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillater appen å angi en pålitelig agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Start innstillingsmenyen til tillitsagenten."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Tillater apper å starte en aktivitet som endrer atferden til tillitsagenter."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Tilknytt en tillitsagent-tjeneste."</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Gir appen tillatelse til å knyttes til en tillitsagent-tjeneste."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Samhandling med oppdateringer og gjenopprettingssystem"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Velg året"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Jobb-<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruke lås-til-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lås-til-app låser skjermen i en enkelt app.\n\nFor å avslutte trykker du på og holder nede nylige apper-knappen $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEI"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Start lås-til-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Avslutt lås-til-app"</string>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a1a5357..b332e76 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Vliegmodus is AAN"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Vliegmodus is UIT"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Instellingen"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Nu vergrendelen"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Hiermee wordt de houder toegestaan zich te verbinden met de hoofdinterface van een achtergrond. Nooit vereist voor normale apps."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"binden aan een service voor spraakinteractie"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Hiermee kan de houder binden aan de hoofdinterface van een service voor spraakinteractie. Nooit vereist voor normale apps."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gesproken trefwoorden beheren"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Hiermee kan de houder de trefwoorden voor gesproken hotword-detectie beheren. Nooit vereist voor normale apps."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"verbinding maken met een extern display"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Hiermee wordt de houder toegestaan verbinding te maken met de hoofdinterface van een extern display. Nooit vereist voor normale apps."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"verbinden met een widgetservice"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Hiermee kan de app de kalibratieparameters van het aanraakscherm aanpassen. Nooit vereist voor normale apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"toegang tot DRM-certificaten"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Toestaan dat een app DRM-certificaten registreert en gebruikt. Nooit vereist voor normale apps."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Handover-overdrachtsuitzendingen ontvangen."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Hiermee kan informatie over de handover-overdrachtsstatus worden ontvangen."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Wachtwoordregels instellen"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"De lengte en tekens beheren die zijn toegestaan in wachtwoorden voor schermontgrendeling."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Pogingen voor schermontgrendeling bijhouden"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op uw telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"een alarm instellen"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Hiermee kan de app een alarm instellen in een geïnstalleerde wekkerapp. Deze functie wordt door sommige wekkerapps niet geïmplementeerd."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"voicemails verwijderen"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Hiermee kan de app berichten verwijderen uit de inbox van uw voicemail."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"voicemail toevoegen"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Hiermee kan de app berichten toevoegen aan de inbox van uw voicemail."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"alle voicemails lezen"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Hiermee kan de app al uw voicemails lezen."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"geolocatierechten voor browser aanpassen"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Hiermee kan de app de geolocatierechten van de browser aanpassen. Schadelijke apps kunnen dit gebruiken om locatiegegevens te verzenden naar willekeurige websites."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakketten controleren"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Toestaan dat een app controleert op wijzigingen in de trust-status."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Een trust-agent aanleveren."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Hiermee kan een app een trust-agent aanleveren."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Menu met instellingen voor vertrouwensagent starten."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Hiermee kan een app een activiteit starten waarmee het gedrag van de vertrouwensagent wordt gewijzigd."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Binden aan een trust-agentservice"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Toestaan dat een app wordt gebonden aan een trust-agentservice."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interactie met update- en herstelsysteem"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Jaar selecteren"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> geselecteerd"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> verwijderd"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Lock-to-app gebruiken?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Met Lock-to-app wordt het scherm vergrendeld in één app.\n\nAls u dit wilt afsluiten, houdt u de knop \'Recente apps\' $ ingedrukt"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Lock-to-app starten"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Lock-to-app afsluiten"</string>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 76f1521..c2be792 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Tryb samolotowy jest włączony"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Tryb samolotowy jest wyłączony"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Ustawienia"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Zablokuj teraz"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Pozwala na tworzenie powiązania z interfejsem najwyższego poziomu tapety. Nieprzeznaczone dla zwykłych aplikacji."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"powiąż z interaktorem głosowym"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Zezwala na tworzenie powiązania z interfejsem najwyższego poziomu usługi interakcji głosowej. Nieprzeznaczone dla zwykłych aplikacji."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"zarządzanie frazami głosowymi"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Pozwala zarządzać frazami używanymi do wykrywania słów-kluczy w komendach głosowych. Nie powinno być nigdy potrzebne w zwykłych aplikacjach."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"powiązanie z wyświetlaczem zdalnym"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Zezwala na tworzenie powiązania z interfejsem najwyższego poziomu wyświetlacza zdalnego. Nieprzeznaczone dla zwykłych aplikacji."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"powiązanie z usługą widżetów"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Zezwala aplikacji na modyfikowanie parametrów kalibracji ekranu dotykowego. Nieprzeznaczone dla zwykłych aplikacji."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"dostęp do certyfikatów DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Zezwala aplikacji na dodanie i używanie certyfikatów DRM. Nieprzeznaczone dla zwykłych aplikacji."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Otrzymywanie informacji o transferze."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Pozwala na odbieranie informacji o stanie transferu."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Określ reguły hasła"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrolowanie długości haseł odblokowania ekranu i dozwolonych w nich znaków"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitoruj próby odblokowania ekranu"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Pozwala aplikacji na modyfikowanie historii i zakładek przeglądarki zapisanych na telefonie. Aplikacja będzie mogła usunąć lub zmodyfikować dane przeglądarki. Uwaga: to uprawnienie może nie być egzekwowane przez przeglądarki innych firm oraz inne aplikacje z możliwością przeglądania internetu."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ustawianie alarmu"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Pozwala aplikacji na ustawienie alarmu w zainstalowanej aplikacji budzika. Funkcja ta może nie być zaimplementowana w niektórych aplikacjach tego typu."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"usuwaj wiadomości głosowe"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Pozwala aplikacji na usuwanie wiadomości z Twojej skrzynki głosowej."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodawanie poczty głosowej"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Pozwala aplikacji na dodawanie wiadomości do skrzynki odbiorczej poczty głosowej."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"odczyt całej poczty głosowej"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Zezwala aplikacji na odczyt całej Twojej poczty głosowej."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modyfikowanie pozwoleń przeglądarki dotyczących lokalizacji geograficznej"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Pozwala aplikacji na modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej. Złośliwe aplikacje mogą używać tej opcji do wysyłania informacji o lokalizacji do dowolnych witryn."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"weryfikowanie pakietów"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Zezwala aplikacji na monitorowanie zmian w stanie zaufania."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Dostarczaj agenta zaufania."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Zezwala aplikacji na dostarczanie agenta zaufania."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Uruchom menu ustawień zaufanego agenta."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umożliwia aplikacji uruchamianie czynności, która zmienia działanie zaufanego agenta."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Powiąż z usługą agenta zaufania"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Zezwala aplikacji na powiązanie z usługą agenta zaufania."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interakcja z systemem odzyskiwania i aktualizacjami"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Wybierz rok"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Wybrałeś <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> usunięte"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (praca)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Chcesz użyć funkcji lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Po włączeniu funkcji lock-to-app na ekranie będzie wyświetlona pojedyncza aplikacja.\n\nAby wyłączyć tę funkcję, naciśnij i przytrzymaj przycisk ostatnio używanych aplikacji $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NIE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Włącz funkcję lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Wyłącz funkcję lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 471e41f..ca0c028 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"O modo de voo está ativado"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"O modo de voo está desativado"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Definições"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite ao titular vincular-se à interface de nível superior de uma imagem de fundo. Nunca deverá ser necessário para aplicações normais."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"vincular a um interlocutor de voz"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite que o titular vincule a interface de nível superior de um serviço de interação de voz. Nunca deverá ser necessário para aplicações normais."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gerir expressões-chave de voz"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite ao proprietário gerir as expressões-chave para a deteção de palavras de ativação de voz. Nunca deverá ser necessário para aplicações normais."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"associar a um ecrã remoto"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite ao detentor associar a interface de nível superior a um ecrã remoto. Nunca deve ser necessário para aplicações normais."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"vincular a um serviço de widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permite à aplicação modificar os parâmetros de calibragem do ecrã tátil. Esta funcionalidade nunca deverá ser necessária para aplicações normais."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"Aceder a certificados DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permite que uma aplicação forneça e utilize certificados DRM. Nunca deverá ser necessário para aplicações normais."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Receber transmissões de transferência da entrega."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Permite a receção de informações do estado de transferência da entrega."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Definir regras de palavra-passe"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controlar o comprimento e os caracteres permitidos nas palavras-passe de desbloqueio do ecrã."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitorizar tentativas de desbloqueio do ecrã"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que a aplicação modifique o histórico do Navegador ou marcadores guardados no telemóvel. Isto pode permitir que a aplicação apague ou modifique dados do Navegador. Nota: esta autorização pode não ser aplicada por navegadores de terceiros ou outras aplicações com capacidades de navegação na Web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"definir um alarme"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que a aplicação defina um alarme numa aplicação de despertador instalada. Algumas aplicações de despertador podem não integrar esta funcionalidade."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"remover mensagens de correio de voz"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Permite que a aplicação remova mensagens da sua caixa de entrada de correio de voz."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"adicionar correio de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que a aplicação adicione mensagens à sua caixa de entrada de correio de voz."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ler todo o correio de voz"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que a aplicação leia todo o correio de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificar permissões de geolocalização do Navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que a aplicação modifique as permissões de geolocalização do navegador. As aplicações maliciosas podem usar isto para permitir o envio de informações de localização para Web sites arbitrárias."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar pacotes"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que uma aplicação registe alterações no trust state."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fornecer um agente fidedigno."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que uma aplicação forneça um agente fidedigno."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Inicie o menu de definições do agente de fidedignidade."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que uma aplicação inicie uma atividade que altere o comportamento do agente de fidedignidade."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Vincular a um serviço de trust agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que uma aplicação fique vinculada a um serviço de trust agent."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interagir com o sistema de recuperação e de atualização"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Selecionar ano"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Utilizar o Lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"A funcionalidade Lock-to-app bloqueia o ecrã numa única aplicação.\n\nPara sair, prima sem soltar o botão $ das aplicações recentes"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NÃO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar o Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Sair do Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0ed34e2..e5fb0ed 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avião ATIVADO"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modo avião DESATIVADO"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Configurações"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite que o proprietário utilize interface de nível superior de um plano de fundo. Nunca deve ser necessário para aplicativos normais."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"associar a um interagente de voz"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite que o proprietário use a interface de nível superior de um serviço de interação de voz. Não deve ser necessário para aplicativos comuns."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gerenciar frases-chave de voz"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite ao titular gerenciar as frases-chave para detecção de hotword por voz. Isso nunca será necessário para aplicativos normais."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"usar uma tela remota"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite que o proprietário use a interface de nível superior de uma tela remota. Não deve ser necessário para aplicativos comuns."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"sujeitar-se a um serviço de widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permite que o aplicativo modifique os parâmetros de calibragem da tela sensível ao toque. Não deve ser necessário para aplicativos normais."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"acessar certificados de DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permite que o aplicativo provisione e use certificados de DRM. Não deve ser necessário para aplicativos comuns."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Definir regras para senha"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Controlar o tamanho e os caracteres permitidos nas senhas de desbloqueio de tela."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitorar tentativas de desbloqueio da tela"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que o aplicativo modifique o histórico ou os favoritos do navegador armazenados no telefone. Pode permitir que o aplicativo apague ou modifique os dados do navegador. Observação: pode não ser aplicável a navegadores de terceiros e outros aplicativos com capacidade de navegação na web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"definir um alarme"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que o aplicativo defina um alarme em um aplicativo despertador instalado. Alguns aplicativos despertador podem não implementar este recurso."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"adicionar correio de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que o aplicativo adicione mensagens a sua caixa de entrada do correio de voz."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ler todo o correio de voz"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que o aplicativo leia todos os seus correios de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifique as permissões de geolocalização de seu navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que o aplicativo modifique as permissões de geolocalização do navegador. Aplicativos maliciosos podem usar isso para permitir o envio de informações locais para sites arbitrários."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar pacotes"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite que o aplicativo detecte alterações no estado de confiança."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Fornecer um agente de confiança."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite que um aplicativo forneça um agente de confiança."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Abra o menu de configurações do agente de confiança."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite que um aplicativo inicie uma atividade que altera o comportamento do agente de confiança."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Associar a um serviço de agente de confiança"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite que o aplicativo se associe a um serviço de agente de confiança."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interagir com o sistema de atualizações e recuperação"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Selecione o ano"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> excluído"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Trabalho: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Usar fixar em aplicativo?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"\"Fixar em aplicativo\" congela a exibição em um único aplicativo.\n\nPara sair, pressione e segure o botão de aplicativos recentes $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NÃO"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar Fixar em aplicativo"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Sair de Fixar em aplicativo"</string>
 </resources>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index c1341c2..58fe4af 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -242,6 +242,8 @@
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Il modus d\'aviun è deactivà."</string>
     <!-- no translation found for global_action_settings (1756531602592545966) -->
     <skip />
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <!-- no translation found for status_bar_notification_info_overflow (5301981741705354993) -->
     <skip />
     <string name="safeMode" msgid="2788228061547930246">"Modus segirà"</string>
@@ -634,6 +636,10 @@
     <skip />
     <!-- no translation found for permdesc_bindVoiceInteraction (2345721766501778101) -->
     <skip />
+    <!-- no translation found for permlab_manageVoiceKeyphrases (1252285102392793548) -->
+    <skip />
+    <!-- no translation found for permdesc_manageVoiceKeyphrases (8476560722907530008) -->
+    <skip />
     <!-- no translation found for permlab_bindRemoteDisplay (1782923938029941960) -->
     <skip />
     <!-- no translation found for permdesc_bindRemoteDisplay (1261242718727295981) -->
@@ -885,6 +891,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 />
@@ -1236,6 +1246,10 @@
     <skip />
     <!-- no translation found for permdesc_accessDrmCertificates (8073288354426159089) -->
     <skip />
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <!-- no translation found for policylab_limitPassword (4497420728857585791) -->
     <skip />
     <!-- no translation found for policydesc_limitPassword (3252114203919510394) -->
@@ -1654,10 +1668,18 @@
     <skip />
     <!-- no translation found for permdesc_setAlarm (316392039157473848) -->
     <skip />
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <!-- no translation found for permlab_addVoicemail (5525660026090959044) -->
     <skip />
     <!-- no translation found for permdesc_addVoicemail (6604508651428252437) -->
     <skip />
+    <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
+    <skip />
+    <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
+    <skip />
     <!-- no translation found for permlab_writeGeolocationPermissions (5962224158955273932) -->
     <skip />
     <!-- no translation found for permdesc_writeGeolocationPermissions (1083743234522638747) -->
@@ -2182,6 +2204,10 @@
     <skip />
     <!-- no translation found for permdesc_provide_trust_agent (3865702641053068148) -->
     <skip />
+    <!-- no translation found for permlab_launch_trust_agent_settings (7494179366945389098) -->
+    <skip />
+    <!-- no translation found for permdesc_launch_trust_agent_settings (985453787420853278) -->
+    <skip />
     <!-- no translation found for permlab_bind_trust_agent_service (8242093169457695334) -->
     <skip />
     <!-- no translation found for permdesc_bind_trust_agent_service (7041930026024507515) -->
@@ -2863,4 +2889,18 @@
     <skip />
     <!-- no translation found for deleted_key (7659477886625566590) -->
     <skip />
+    <!-- no translation found for managed_profile_label_badge (2355652472854327647) -->
+    <skip />
+    <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+    <skip />
+    <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+    <skip />
+    <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+    <skip />
+    <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+    <skip />
+    <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+    <skip />
+    <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index ef5a384..0424909 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modul Avion este ACTIVAT"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modul avion este DEZACTIVAT"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Setări"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Permite proprietarului să se conecteze la interfaţa de nivel superior a unei imagini de fundal. Nu ar trebui să fie niciodată necesară pentru aplicaţiile obişnuite."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"conectare la un serviciu de interacțiune vocală"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Permite proprietarului să se conecteze la interfața de nivel superior a unui serviciu de interacțiune vocală. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"gestionarea expresiilor cheie vocale"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Permite titularului să gestioneze expresiile cheie pentru detectarea expresiei de activare. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"conectare la un ecran la distanță"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Permite proprietarului să se conecteze la interfața de nivel superior a unui ecran la distanță. Nu ar trebui să fie niciodată necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"conectare la un serviciu widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Permite aplicației să modifice parametrii de calibrare a ecranului tactil. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"accesează certificatele DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Permite unei aplicații să furnizeze și să utilizeze certificate DRM. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Setaţi reguli pentru parolă"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Stabiliţi lungimea şi tipul de caractere permise în parolele pentru deblocarea ecranului."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Monitorizaţi încercările de deblocare a ecranului"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite aplicaţiei să modifice istoricul Browserului sau marcajele stocate pe telefon. În acest fel, aplicaţia poate şterge sau modifica datele din Browser. Notă: această permisiune nu poate fi aplicată de browsere terţă parte sau de alte aplicaţii cu capacităţi de navigare pe web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"setează o alarmă"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite aplicaţiei să seteze o alarmă într-o aplicaţie de ceas cu alarmă instalată. Este posibil ca unele aplicaţii de ceas cu alarmă să nu implementeze această funcţie."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"adăugare mesagerie vocală"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite aplicaţiei să adauge mesaje în Mesaje primite în mesageria vocală."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"acces la toate mesajele vocale"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite aplicației să acceseze toate mesajele vocale."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificare permisiuni pentru locaţia geografică a browserului"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite aplicaţiei să modifice permisiunile privind locaţia geografică a browserului. Aplicaţiile rău intenţionate pot utiliza această permisiune pentru a permite trimiterea informaţiilor privind locaţia către site-uri web arbitrare."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificare pachete"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Permite unei aplicații să detecteze modificările în starea de încredere."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Indicați un agent de încredere."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Permite unei aplicații să indice un agent de încredere."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Lansați meniul de setări pentru agentul de încredere."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Permite unei aplicații să lanseze o activitate care schimbă comportamentul agentului de încredere."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Asocierea la un serviciu „agenți de încredere”."</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Permite unei aplicații să se asocieze la un serviciu „agent de încredere”."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interacțiune cu sistemul de recuperare și de actualizare"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Selectați anul"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selectat"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> a fost șters"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de serviciu"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Utilizați Blocarea aplicației?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Blocarea aplicației blochează ecranul într-o singură aplicație.\n\nPentru a ieși, apăsați lung pe butonul pentru aplicații recente $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NU"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"PORNIȚI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Porniți Blocarea aplicației"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Ieșiți din Blocarea aplicației"</string>
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index ac68bda..f6e6d5a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Выключить"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Включить"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Настройки"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Приложение сможет подключаться к базовому интерфейсу службы обоев. Это разрешение не используется обычными приложениями."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"Подключение к службам голосового взаимодействия"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Приложение сможет подключаться к базовому интерфейсу служб голосового взаимодействия. Это разрешение обычно используется только специальными приложениями."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Управление ключевыми фразами для распознавания голосовых команд"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Управление ключевыми фразами для функции распознавания голосовых команд. Это разрешение обычно используется только специальными приложениями."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"Подключение к удаленному дисплею"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Приложение сможет подключаться к базовому интерфейсу удаленного дисплея. Это разрешение обычно используется только специальными приложениями."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"Подключение к службе виджетов"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Приложение сможет изменять параметры калибровки сенсорного экрана. Это разрешение обычно используется только специальными приложениями."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"Доступ к сертификатам DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Приложение сможет синхронизировать и использовать сертификаты DRM (разрешение актуально только для специальных приложений)."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Правила выбора паролей"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролировать длину и символы при вводе паролей для снятия блокировки экрана."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Отслеживать попытки снятия блокировки экрана"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Приложение сможет изменять историю или закладки браузера, сохраненные на телефоне, а также удалять и изменять данные браузера. Обратите внимание: браузеры независимых поставщиков или другие приложения для просмотра веб-страниц могут не применять это разрешение."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"Установка будильника"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Приложение сможет настраивать будильник. Функция поддерживается не во всех программах."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"Добавление голосовых сообщений"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Приложение сможет добавлять голосовые сообщения в папку \"Входящие\"."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Доступ к голосовой почте"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Чтение голосовых сообщений."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Изменение прав доступа к геоданным в браузере"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Приложение сможет изменять настройки доступа к геоданным в браузере. Вредоносные программы смогут таким образом отправлять информацию о местоположении на любые веб-сайты."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Проверка пакетов"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Приложение сможет отслеживать изменения в статусе доверия."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Доверенный агент"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Предоставление доверенных агентов."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Запуск настроек Trust Agent"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Запуск меню, позволяющего управлять настройками Trust Agent."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Подключение к службе Trust Agents"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Приложение сможет подключаться к службе Trust Agents."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Взаимодействовать с системой восстановления и обновлениями"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Выберите год"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Выбран элемент <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Цифра <xliff:g id="KEY">%1$s</xliff:g> удалена"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Рабочий <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Использовать блокировку в приложении?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Функция блокирует переход в другие приложения.\n\nЧтобы выключить блокировку, нажмите и удерживайте кнопку \"Недавние приложения\" $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"НЕТ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ДА"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Запуск блокировки в приложении"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Выключить блокировку"</string>
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 20674b0..2ae3a8e 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Režim V lietadle je ZAPNUTÝ"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Režim V lietadle je VYPNUTÝ"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Nastavenia"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania tapety. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"viazanie na hlasovú interakciu"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania služby hlasovej interakcie. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"správa vyslovených kľúčových fráz"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Umožňuje držiteľovi spravovať kľúčové frázy pre rozpoznávanie vyslovených kľúčových slov. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"viazať na vzdialený displej"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Umožňuje držiteľovi viazať sa na najvyššiu úroveň rozhrania vzdialeného displeja. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"viazať sa k službe miniaplikácie"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Umožňuje aplikácii upraviť parametre kalibrácie dotykovej obrazovky. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"prístup k certifikátom DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Umožňuje aplikácii vydávať a používať certifikáty DRM. Bežné aplikácie by toto povolenie nemali nikdy potrebovať."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Nastaviť pravidlá pre heslo"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Ovládanie dĺžky hesiel na odomknutie obrazovky a v nich používané znaky."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Sledovať pokusy o odomknutie obrazovky"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Umožňuje aplikácii upraviť históriu prehliadača alebo záložky uložené v telefóne. Aplikácia s týmto povolením môže vymazať alebo upraviť údaje prehliadača. Poznámka: Toto povolenie nemôžu vynucovať prehliadače tretích strán ani žiadne ďalšie aplikácie umožňujúce prehliadanie webu."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"nastaviť budík"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikácii nastaviť budík v nainštalovanej aplikácii budík. Niektoré aplikácie budíka nemusia túto funkciu implementovať."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"pridať hlasovú schránku"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Umožní aplikácii pridávať správy do doručenej pošty hlasovej schránky."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"čítanie všetkých hlasových schránok"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Umožňuje aplikácii čítať všetky vaše hlasové schránky."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"zmeniť povolenia prehliadača poskytovať informácie o zemepisnej polohe"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Umožňuje aplikácii zmeniť povolenia prehliadača na poskytovanie údajov o zemepisnej polohe. Škodlivé aplikácie to môžu použiť na odosielanie informácií o polohe ľubovoľným webovým stránkam."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"overiť balíky"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Umožňuje aplikácii reagovať na zmeny stavu dôveryhodnosti."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Poskytnúť dôveryhodného agenta"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Umožňuje aplikácii poskytnúť dôveryhodného agenta."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Spustenie ponuky nastavení dôveryhodného agenta"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Umožňuje aplikácii spustiť aktivitu, ktorá zmení správanie dôveryhodného agenta."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Viazanie sa na službu zástupcu dôveryhodnosti"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Umožňuje aplikácii viazať sa na službu zástupcu dôveryhodnosti."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interakcia so systémom aktualizácií a obnovenia"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Bola vybratá položka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Číslo <xliff:g id="KEY">%1$s</xliff:g> bolo odstránené"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Práca – <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Použiť uzamknutie na aplikáciu?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Funkcia Uzamknutie na aplikáciu uzamkne displej na zobrazovanie jednej aplikácie.\n\nTúto funkciu ukončíte stlačením a podržaním tlačidla nedávnych aplikácií $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NIE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"SPUSTIŤ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Spustiť funkciu Uzamknutie na aplikáciu"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Ukončiť funkciu Uzamknutie na aplikáciu"</string>
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 01c3d0f..873dae8 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Način za letalo je VKLOPLJEN"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Način za letalo je IZKLOPLJEN"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Nastavitve"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Imetniku omogoča povezavo z vmesnikom ozadja najvišje ravni. Tega nikoli ni treba uporabiti za navadne programe."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"povezovanje z glasovnim interaktorjem"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Imetniku omogoča povezovanje z vmesnikom storitve glasovne interakcije najvišje ravni. Tega ni treba nikoli uporabiti za navadne aplikacije."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"upravljanje glasovnih ključnih besednih zvez"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Imetniku omogoča upravljanje ključnih besednih zvez za zaznavanje sprožilne besede. Tega ni treba nikoli uporabiti za navadne aplikacije."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"povezava z oddaljenim prikazom"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Imetniku omogoča povezovanje z vmesnikom oddaljenega prikaza najvišje ravni. Tega ni treba nikoli uporabiti za navadne aplikacije."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"poveži s storitvijo pripomočka"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Aplikaciji dovoli spreminjanje parametrov za umerjanje zaslona na dotik. Tega ni treba nikoli uporabiti za navadne aplikacije."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"dostop do potrdil za upravljanje digitalnih pravic"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Aplikaciji omogoča pripravo in uporabo potrdil za upravljanje digitalnih pravic. To naj ne bi bilo nikoli potrebno za običajne aplikacije."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Nastavitev pravil za geslo"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Nadzor nad dolžino in znaki, ki so dovoljeni v geslih za odklepanje zaslona."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"nadzor nad poskusi odklepanja zaslona"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Aplikaciji omogoča spreminjanje zgodovine ali zaznamkov brskalnika v telefonu. S tem lahko aplikacija izbriše ali spremeni podatke v brskalniku. Opomba: Tega dovoljenja ne morejo uveljavljati drugi brskalniki ali aplikacije, s katerimi je mogoče brskati po spletu."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"nastavitev alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Programu omogoča nastavitev alarma v nameščenem programu budilke. Nekateri programi budilke morda nimajo te funkcije."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"dodajanje odzivnika"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Programu omogoča dodajanje sporočil prejetim sporočilom odzivnika."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"branje vseh sporočil v odzivniku"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Aplikaciji omogoča branje vseh sporočil v odzivniku."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Spreminjanje dovoljenj za geolokacijo brskalnika"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Programu omogoča spreminjanje geolokacijskih dovoljenj v brskalniku. Zlonamerni programi lahko to izkoristijo za pošiljanje podatkov o lokaciji poljubnim spletnim mestom."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"preveri pakete"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Aplikaciji dovoli spremljanje sprememb stanja zaupanja."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Navedba posrednika zaupanja."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Aplikaciji dovoli navesti posrednika zaupanja."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Odpri meni z nastavitvami posrednika zaupanja."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Dovoli aplikaciji zagon dejavnosti, ki spremeni način delovanja posrednika zaupanja."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Povezovanje s storitvijo posrednikov zaupanja"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Aplikaciji dovoli povezovanje s storitvijo posrednikov zaupanja."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Uporaba sistema za posodobitev in obnovitev"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Izberite leto"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Izbrano: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Številka <xliff:g id="KEY">%1$s</xliff:g> je izbrisana"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za delo"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Želite uporabiti zaklepanje v aplikacijo?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Zaklepanje v aplikacijo zaklene zaslon v eni aplikaciji.\n\nČe želite zapustiti ta način, pritisnite in pridržite gumb za nedavne aplikacije $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ZAŽENI"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Zagon zaklepanja v aplikacijo"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Izhod iz zaklepanja v aplikacijo"</string>
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 074d4f7..4a5b2de 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Режим рада у авиону је УКЉУЧЕН"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Режим рада у авиону је ИСКЉУЧЕН"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Подешавања"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Дозвољава власнику да се повеже са интерфејсом позадине највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"повежи са гласовним интерактором"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Дозвољава власнику да се повеже са интерфејсом највишег нивоа услуге гласовне интеракције. Не би требало никада да буде потребно за уобичајене апликације."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"управљање усменим кључним фразама"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Дозвољава власнику да управља кључним фразама за откривање усмених актуелних речи. Никада не би требало да буде потребно за уобичајене апликације."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"повезивање са удаљеним екраном"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Дозвољава власнику да се повеже са интерфејсом удаљеног екрана највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"обавезивање на услугу виџета"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Дозвољава апликацији да модификује параметре калибрације додирног екрана. Не би требало да буде потребно за нормалне апликације."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"приступ DRM сертификатима"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Дозвољава апликацији да додељује и користи DRM сертификате. Никада не би требало да се користи за уобичајене апликације."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Подешавање правила за лозинку"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролишите дужину и знакове дозвољене у лозинкама за откључавање екрана."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Надгледање покушаја откључавања екрана"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Дозвољава апликацији да мења историју Прегледача или обележиваче ускладиштене на телефону. Ово може да омогући апликацији да брише или мења податке Прегледача. Напомена: Ова дозвола се можда на примењује на прегледаче треће стране и друге апликације са могућношћу веб прегледања."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"подешавање аларма"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Дозвољава апликацији да подеси аларм у инсталираној апликацији будилника. Неке апликације будилника можда не примењују ову функцију."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"додавање говорне поште"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Дозвољава апликацији да додаје поруке у пријемно сандуче говорне поште."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"читај сву говорну пошту"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Дозвољава апликацији да чита говорну пошту."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"измена дозвола за географске локације Прегледача"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Дозвољава апликацији да измени дозволе Прегледача за утврђивање географске локације. Злонамерне апликације то могу да злоупотребе и искористе за слање информација о локацији насумичним веб сајтовима."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"верификовање пакета"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Дозвољава апликацији да прати промене Trust стања."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Обезбеђивање поузданог агента."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Дозвољава апликацији да обезбеди поузданог агента."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Покрени мени подешавања поузданог агента."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Дозвољава апликацији да покреће активност која мења понашање поузданог агента."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Везивање за услугу Trust agents"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Дозвољава апликацији да се веже за услугу Trust agents."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Интеракција са системом за ажурирање и опоравак"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Изаберите годину"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Изабрали сте <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Избрисали сте <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> на послу"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Желите ли да користите закључавање у апликацији?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Закључавање у апликацији закључава екран у само једној апликацији.\n\nДа бисте изашли притисните и држите дугме за недавне апликације $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"НЕ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ПОКРЕНИ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Покрени закључавање у апликацији"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Излазак из Закључавања у апликацији"</string>
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 6b5431f..bf1271a 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flygplansläge är AKTIVERAT"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flygplansläge är INAKTIVERAT"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Inställningar"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Innehavaren kan binda till den översta nivåns gränssnitt för en bakgrund. Ska inte behövas för vanliga appar."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bind till en röstkomponent"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en rösttjänst. Ska inte behövas för vanliga appar."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"hantera sökfraser för röstkommandon"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Innehavaren får hantera sökfraser för identifiering av röstkommandon. Detta ska inte behövas för vanliga appar."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bind till en fjärrskärm"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en fjärrskärm. Ska inte behövas för vanliga appar."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bind till en widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Tillåter att appen ändrar kalibreringsparametrarna för pekskärmen. Detta behövs aldrig för vanliga appar."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"tillgång till DRM-certifikat"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Tillåter att en app tillhandahåller och använder DRM-certifikat. Behövs inte för vanliga appar."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Ta emot överföringssändningar."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Tillåter att information om överföringsstatus tas emot."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Ange lösenordsregler"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Bestäm hur många och vilka tecken som är tillåtna i skärmlåsets lösenord."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Övervaka försök att låsa upp skärmen"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Tillåter att appen ändrar historiken för besökta sidor i webbläsaren eller bokmärken som sparats på telefonen. Det kan innebära att appen kan ta bort eller ändra webbläsarinformation. Observera att den här behörigheten kanske inte är tillämplig för webbläsare från tredje part eller andra appar med surffunktion."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ställa in ett alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillåter att appen ställer in ett alarm i en befintlig alarmapp. Vissa alarmappar har inte den här funktionen."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"ta bort röstmeddelanden"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Tillåter att appen tar bort meddelanden från röstbrevlådans inkorg."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"lägg till röstbrevlåda"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Gör att appen lägger till meddelanden i röstbrevlådans inkorg."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"läsa alla röstmeddelanden"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Tillåter att appen läser alla dina röstmeddelanden."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Ändra geografisk plats för webbläsaren"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Tillåter att appen ändrar webbläsarens behörigheter för geografisk plats. Skadliga appar kan använda detta för att tillåta att platsinformation skickas till godtyckliga webbplatser."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"kontrollera paket"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Tillåter att en app lyssnar efter ändringar i den betrodda agentens status."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Tillhandahåll en betrodd agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Tillåter att en app tillhandahåller en betrodd agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Öppna inställningsmenyn för betrodda agenter."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Tillåter att en app startar en aktivitet som ändrar den betrodda agentens beteende."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bind till en tjänst från en betrodd agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Tillåter att en app binds vid en tjänst från en betrodd agent."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Interagera med uppdaterings- och återställningssystemet"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Välj år"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> har markerats"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> har tagits bort"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> för arbetet"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Vill du använda Lås till app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Med funktionen Lås till app låses skärmen i en enskild app.\n\nAvsluta genom att trycka länge på knappen för de senaste apparna $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"STARTA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Starta Lås till app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Avsluta Lås till app"</string>
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 9c958ac..914c0b3 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Hali ya ndege IMEWASHWA"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Hali ya ndege IMEZIMWA"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Mipangilio"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Funga sasa"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Inaruhusu kishikiliaji kushurutisha kwa kusano ya kiwango cha juu cha mandhari. Haipaswi kamwe kuhitajika kwa programu za kawaida."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bandika kwenye mwingiliano wa sauti"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Humruhusu mmiliki kubandika kwenye kiolesura cha hali ya juu cha huduma ya muingiliano wa sauti. Isihitajike kamwe kwa programu za kawaida."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"Simamia misimbo kuu ya sauti"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Huruhusu mmiliki kusimamia misimbo kuu ya utambuzi wa neno tekelezi la sauti. Haipaswi kuhitajika kwa programu za kawaida kamwe."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"fungisha kwenye mwonekano wa mbali"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Huruhusu mtumiaji kujifungia kiolesura cha kiwango cha juu cha mwonekano wa mbali. Haipaswi kuhitajika kwa programu za kawaida."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"funga kwenye huduma ya widget"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Huruhusu programu kubadilisha vigezo vya urekebishaji vya skrini ya kugusa. Havipaswi kuhitajika kamwe kwa programu za kawaida."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"fikia vyeti vya DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Huruhusu programu kwa utoaji na matumizi ya vyeti vya DRM. Havifahi kuhitajika kwa ajili ya programu za kawaida."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Pokea matangazo ya makabidhiano ya uhamisho."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Huruhusu upokeaji wa maelezo ya hali ya makabidhiano ya uhamisho."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Kuweka kanuni za nenosiri"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kudhibiti urefu na herufi zinazoruhusiwa katika manenosiri ya kufungua skrini."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Kuhesabu mara ambazo skrini inajaribu kufunguliwa"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Inaruhusu programu kurekebisha historia ya Kivinjari au alamisho zilizohifadhiwa kwenye simu yako. Hii huenda ikaruhusu programu kufuta au kurekebisha data ya Kivinjari. Kumbuka: huenda idhini hii isitekelezwe na vivinjari vingine au programu nyingine zenye uwezo wa kuvinjari wavuti."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"weka kengele"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Inaruhusu programu kuweka kengele katika programu iliyosakinishwa ya kengele. Programu zingine za kengele zinawezakosa kutekeleza kipengee hiki."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"ondoa ujumbe wa sauti"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Huruhusu programu kuondoa ujumbe kwenye kikasha chako cha ujumbe wa sauti."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"ongeza barua ya sauti"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Huruhusu programu kuongeza mawasiliano kwenye kikasha cha ujumbe wa sauti."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"soma ujumbe wote wa sauti"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Huruhusu programu isome ujumbe wako wote wa sauti."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Rekebisha vibali vya Kivinjari cha eneo la jio"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Inaruhusu programu kurekebisha ruhusa za eneo la jio za kivinjari. Programu hasidi zinaweza tumia hii kuruhusu kutuma taarifa ya eneo kwa wavuti holela."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"thibitisha furushi"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Huruhusu programu kusikiliza mabadiliko katika hali ya kuaminiwa."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Toa wakala wa uaminifu."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Huruhusu programu kutoa wakala wa uaminifu."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Fungua menyu ya mipangilio ya madalali wa kuaminiwa."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Huruhusu programu kufungua kitendo ambacho hubadilisha tabia ya madalali wa kuaminiwa."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Funga kwenye huduma ya dalali wa kuaminiwa"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Huruhusu programu kufungamanisha kwenye huduma ya dalali wa kuaminiwa."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Ingiliana na sasisho na mfumo wa kurejesha"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Chagua mwaka"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kimechaguliwa"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> kimefutwa"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Ya kazini <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Ungependa kutumia lazimisha kutumia programu?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lazimisha kutumia programu katika programu moja. \n\n Ili uondoke bonyeza na ushikilie kitufe cha programu za hivi majuzi $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"HAPANA"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"ANZA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Anzisha Lazimisha kutumia programu"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Ondoka kwenye Lazimisha kutumia programu"</string>
 </resources>
diff --git a/core/res/res/values-television/themes.xml b/core/res/res/values-television/themes.xml
index f92ff40..3333f1b 100644
--- a/core/res/res/values-television/themes.xml
+++ b/core/res/res/values-television/themes.xml
@@ -16,8 +16,13 @@
 <resources>
     <style name="Theme.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
     <style name="Theme.Dialog.AppError" parent="Theme.Leanback.Dialog.AppError" />
+    <style name="Theme.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" />
     <style name="Theme.Holo.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" />
+    <style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" />
     <style name="Theme.Holo.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
+    <style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" />
     <style name="Theme.Material.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" />
+    <style name="Theme.Material.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" />
     <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
+    <style name="Theme.Material.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" />
 </resources>
diff --git a/core/res/res/values-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml
index e01caa3..1938675 100644
--- a/core/res/res/values-television/themes_device_defaults.xml
+++ b/core/res/res/values-television/themes_device_defaults.xml
@@ -16,4 +16,6 @@
 <resources>
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Leanback.Dialog.Alert" />
     <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Leanback.Light.Dialog.Alert" />
+    <style name="Theme.DeviceDefault.Dialog.TimePicker" parent="Theme.Leanback.Dialog.TimePicker" />
+    <style name="Theme.DeviceDefault.Light.Dialog.TimePicker" parent="Theme.Leanback.Light.Dialog.TimePicker" />
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 1522f2e..5904777 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"เปิดโหมดใช้งานบนเครื่องบิน"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"โหมดใช้งานบนเครื่องบินปิดทำงานอยู่"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"การตั้งค่า"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"อนุญาตให้ผู้ใช้เชื่อมโยงกับส่วนติดต่อผู้ใช้ระดับสูงสุดของวอลเปเปอร์ ไม่ควรต้องใช้สำหรับแอปพลิเคชันทั่วไป"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"เชื่อมโยงกับโปรแกรมโต้ตอบด้วยเสียง"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"อนุญาตให้ผู้ใช้อุปกรณ์เชื่อมโยงกับอินเทอร์เฟซระดับบนสุดของบริการโต้ตอบด้วยเสียง ไม่จำเป็นสำหรับแอปทั่วไป"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"จัดการเสียงพูดวลีคำหลัก"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"ให้แอปสามารถจัดการวลีคำหลักสำหรับการตรวจหาเสียงพูดคำที่นิยม ไม่จำเป็นสำหรับแอปทั่วไป"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ผูกกับจอแสดงผลระยะไกล"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"อนุญาตให้ผู้ใช้ผูกกับอินเทอร์เฟซระดับสูงสุดของจอแสดงผลระยะไกล ซึ่งแอปพลิเคชันทั่วไปไม่จำเป็นต้องใช้"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"เชื่อมโยงกับบริการวิดเจ็ต"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"อนุญาตให้แอปสามารถปรับพารามิเตอร์การเทียบมาตรฐานของหน้าจอสัมผัส ไม่ควรใช้สำหรับแอปทั่วไป"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"เข้าถึงใบรับรอง DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"ช่วยให้แอปพลิเคชันสามารถจัดสรรและใช้ใบรับรอง DRM ได้ ไม่จำเป็นสำหรับแอปปกติทั่วไป"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"ตั้งค่ากฎรหัสผ่าน"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"ควบคุมความยาวและอักขระที่อนุญาตให้ใช้ในรหัสผ่านการปลดล็อกหน้าจอ"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"ตรวจสอบความพยายามในการปลดล็อกหน้าจอ"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"อนุญาตให้แอปพลิเคชันเปลี่ยนแปลงประวัติหรือบุ๊กมาร์กของเบราว์เซอร์ที่จัดเก็บไว้ในโทรศัพท์ ซึ่งทำให้แอปพลิเคชันสามารถลบหรือเปลี่ยนข้อมูลเบราว์เซอร์ได้ หมายเหตุ: การอนุญาตนี้อาจไม่สามารถใช้งานได้กับเบราว์เซอร์ของบุคคลที่สามหรือแอปพลิเคชันอื่นๆ ที่มีความสามารถในการเรียกดูบนเว็บ"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ตั้งปลุก"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"อนุญาตให้แอปพลิเคชันตั้งเวลาปลุกในแอปพลิเคชันนาฬิกาปลุกที่ติดตั้ง แอปพลิเคชันนาฬิกาปลุกบางรายการอาจไม่ใช้คุณลักษณะนี้"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"เพิ่มข้อวามเสียง"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"อนุญาตให้แอปพลิเคชันเพิ่มข้อความลงในกล่องข้อความเสียงของคุณ"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"อ่านข้อความเสียงทั้งหมด"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"อนุญาตให้แอปอ่านข้อความเสียงทั้งหมดของคุณ"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"แก้ไขการอนุญาตเกี่ยวกับการระบุตำแหน่งทางภูมิศาสตร์ของเบราว์เซอร์"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"อนุญาตให้แอปพลิเคชันแก้ไขการอนุญาตตำแหน่งทางภูมิศาสตร์ของเบราว์เซอร์ แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ในการส่งข้อมูลตำแหน่งไปยังเว็บไซต์ต่างๆ ได้ตามต้องการ"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ยืนยันแพ็กเกจ"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"อนุญาตให้แอปพลิเคชันฟังการเปลี่ยนแปลงที่มีต่อสถานะความน่าเชื่อถือ"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"เสนอตัวแทนที่เชื่อถือได้"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"ช่วยให้แอปพลิเคชันสามารถเสนอตัวแทนที่เชื่อถือได้"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"เปิดเมนูการตั้งค่าตัวแทนที่เชื่อถือได้"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"อนุญาตให้แอปพลิเคชันเปิดกิจกรรมที่เปลี่ยนพฤติกรรมตัวแทนที่เชื่อถือได้"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"ผูกกับบริการของตัวแทนที่เชื่อถือได้"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"อนุญาตให้แอปพลิเคชันผูกกับบริการของตัวแทนที่เชื่อถือได้"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"โต้ตอบกับการอัปเดตและระบบการกู้คืน"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"เลือกปี"</string>
     <string name="item_is_selected" msgid="949687401682476608">"เลือก <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"ลบ <xliff:g id="KEY">%1$s</xliff:g> แล้ว"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g>ที่ทำงาน"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"ใช้การล็อกแอปไหม"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"การล็อกแอปจะล็อกการแสดงไว้ในแอปเดียว\n\nหากต้องการออก ให้กดปุ่มแอปล่าสุด$ค้างไว้"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"ไม่"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"เริ่มต้น"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"เริ่มใช้การล็อกแอป"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"ออกจากการล็อกแอป"</string>
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 330bbe7..1206b66 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Naka-ON ang airplane mode"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Naka-OFF ang airplane mode"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Mga Setting"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Pinapayagan ang may-hawak na sumailalim sa nangungunang interface ng isang wallpaper. Hindi kailanman dapat na kailanganin para sa normal na apps."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"i-bind sa isang voice interactor"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Nagbibigay-daan sa may-hawak na i-bind ang top-level na interface ng isang serbisyo sa pakikipag-ugnayan gamit ang boses. Hindi kailanman dapat kailanganin ng mga normal na app."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"pamahalaan ang mga keyphrase ng boses"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Pinapayagan ang holder na pamahalaan ang mga keyphrase para sa pagtukoy ng hotword ng boses. Hindi dapat kailanman kailanganin para sa mga normal na app."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"magpasaklaw sa isang remote na display"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Binibigyang-daan ang may-hawak na masaklaw ang pinakamataas na antas ng interface ng isang remote na display. Hindi dapat kailanman kailanganin ng normal na apps."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"itali sa serbisyo ng widget"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Pinapayagan ang app na baguhin ang mga parameter sa pag-calibrate ng touch screen. Hindi dapat kailanganin sa normal na apps."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"access sa Mga DRM certificate"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Nagbibigay-daan sa isang application na makapagbigay at gumamit ng mga DRM certficate. Hindi dapat kailanman kailanganin para sa mga normal na app."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Magtakda ng mga panuntunan sa password"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrolin ang haba at mga character na pinapayagan sa mga password sa pag-unlock ng screen."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Subaybayan ang mga pagsubok sa pag-unlock ng screen"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Pinapayagan ang app na baguhin ang kasaysayan o mga bookmark ng Browser na naka-imbak sa iyong telepono. Maaari nitong payagan ang app na burahin o baguhin ang data ng Browser. Tandaan: hindi maaaring ipatupad ang pahintulot na ito ng mga third-party na browser o iba pang mga application na may mga kakayahan sa pagba-browse sa web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"magtakda ng alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Pinapayagan ang app na magtakda ng alarm sa isang naka-install na app ng alarm clock. Maaaring hindi ipatupad ng ilang apps ng alarm clock ang tampok na ito."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"magdagdag ng voicemail"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Pinapayagan ang app na magdagdag ng mga mensahe sa iyong inbox ng voicemail."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"basahin ang lahat ng voicemail"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Pinapayagan ang app na basahin ang lahat ng iyong voicemail."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"baguhin ang mga pahintulot ng geolocation ng Browser"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Pinapayagan ang app na baguhin ang mga pahintulot sa geolocation ng Browser. Maaari itong gamitin ng nakakahamak na apps upang payagan ang pagpapadala ng impormasyon ng lokasyon sa mga hindi tukoy na web site."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"i-verify ang mga package"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Pinapayagan ang isang application na makinig para sa mga pagbabago sa estado ng trust."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Magbigay ng trust agent."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Pinapayagan ang isang application na magbigay ng trust agent."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Ilunsad ang menu ng mga setting ng trust agent."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Nagbibigay-daan sa isang application na maglunsad ng aktibidad na nagbabago sa pagkilos ng trust agent."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Sumailalim sa isang serbisyo ng trust agent"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Pinapayagan ang isang application na sumailalim sa isang serbisyo ng trust agent."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Makipag-ugnay sa system ng pag-update at pagbawi"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Pumili ng taon"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Napili ang <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Tinanggal ang <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> sa Trabaho"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Gagamitin ang lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Nila-lock ng Lock-to-app ang display sa iisang app.\n\nUpang lumabas pindutin nang matagal ang button ng mga kamakailang app $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"HINDI"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"SIMULAN"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Simulan ang Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Lumabas sa Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index d235ca3..c2bc68b 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Uçak modu AÇIK"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Uçak modu KAPALI"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Ayarlar"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Cihazın sahibine, duvar kağıdının en üst düzey arayüzüne bağlanma izni verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"bir ses etkileşimi hizmetine bağlanma"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"İzin sahibinin, bir ses etkileşimi hizmetine ait üst düzey arayüze bağlanmasına izin verir. Normal uygulamalar için hiçbir zaman gerekli değildir."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"sesli anahtar ifadeleri yönet"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"İzin sahibine, söylenen özel kelimeleri algılamak için anahtar ifadeleri yönetme izni verir. Normal uygulamalar için hiçbir zaman gerekli olmamalıdır."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"uzak ekrana bağlan"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"İzin sahibine, bir uzak ekranın en üst düzey arayüzüne bağlanma izni verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bir widget hizmetine bağla"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Uygulamaya, dokunmatik ekranın kalibrasyon parametrelerini değiştirme izni verir. Normal uygulamalar için hiçbir zaman gerekmez."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM sertifikalarına eriş"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Bir uygulamanın DRM sertifikaları için temel hazırlık yapmasına ve bunları kullanmasına izin verir. Normal uygulamalar için hiçbir zaman gerekmez."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Şifre kuralları ayarla"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Ekran kilidini açma şifrelerinde izin verilen uzunluğu ve karakterleri denetleme."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Ekran kilidini açma denemelerini izle"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Uygulamaya Tarayıcı geçmişini ve telefonunuzda depolanan yer işaretlerini değiştirme izni verir. Bu izin, uygulamanın Tarayıcı geçmişini silmesine ve değiştirmesine olanak sağlar. Not: Bu izin, üçüncü taraf cihazlar veya Web\'e göz atma işlevine sahip diğer uygulamalar tarafından kullanılmayabilir."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"alarm ayarla"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Uygulamaya, çalar saat uygulamasının alarmını ayarlama izni verir. Bazı çalar saat uygulamaları bu özelliği uygulayamayabilir."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"sesli mesaj ekle"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Uygulamaya, sesli mesaj gelen kutunuza mesaj ekleme izni verir."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"tüm sesli mesajları oku"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Uygulamanın tüm sesli mesajlarınızı okumasına izin verir."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Tarayıcı\'nın coğrafi konum izinlerini değiştir"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Uygulamaya, Tarayıcı\'nın coğrafi konum izinlerini değiştirme izni verir. Kötü amaçlı uygulamalar keyfi web sitelerine konum bilgisi gönderilmesini sağlamak için bunu kullanabilirler."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"paketleri doğrula"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Bir uygulamanın, güven durumundaki değişiklikleri dinlemesine izin verir."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Güven aracısı sağlama."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Bir uygulamanın güven aracısı sağlamasına izin verir."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Güven aracısı ayarlar menüsünü başlat."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Bir uygulamanın güven aracısının çalışma biçimini değiştiren bir etkinlik başlatmasına izin verir."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Güven aracı hizmetine bağlan"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Bir uygulamanın, güven aracı hizmetine bağlanmasına izin verir."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Güncelleme ve kurtarma sistemiyle etkileşim kur"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Yılı seçin"</string>
     <string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seçildi"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> silindi"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (İş)"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Uygulamaya kilitleme özelliği kullanılsın mı?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Uygulamaya kilitleme özelliği, ekranı tek bir uygulamaya kilitler.\n\nÇıkmak için son uygulamalar düğmesine ($) uzun basın"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"HAYIR"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"BAŞLAT"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Uygulamaya kilitleme özelliğini başlat"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Uygulamaya kilitleme özelliğinden çık"</string>
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ffe735c..e86e533 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Режим польоту ВВІМК."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Режим польоту ВИМК."</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Налаштування"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Блокувати зараз"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Дозволяє власнику прив’язуватися до інтерфейсу верхнього рівня фонового малюнка. Ніколи не застосовується для звичайних програм."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"підключитися до служби голосової взаємодії"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Додаток зможе підключатися до інтерфейсу верхнього рівня служби голосової взаємодії. Звичайні додатки ніколи не використовують цей дозвіл."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"керувати голосовими ключовими фразами"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Власник може керувати ключовими фразами, які розпізнаватимуться як голосові команди швидкого запуску. Звичайні додатки ніколи не використовують цей дозвіл."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"прив’язуватися до віддаленого екрана"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Дозволяє власникові прив’язуватися до інтерфейсу верхнього рівня віддаленого екрана. Ніколи не застосовується для звичайних програм."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"прив\'язувати до служби віджетів"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Програма може змінювати параметри калібрування сенсорного екрана. Ніколи не застосовується для звичайних програм."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"отримувати доступ до сертифікатів DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Дозволяє додатку надавати та використовувати сертифікати DRM. Ніколи не застосовується для звичайних додатків."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Отримувати широкомовні повідомлення про естафетне передавання."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Можна отримувати інформацію про статус естафетного передавання."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Устан. правила пароля"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролювати довжину паролів для розблокування екрана та дозволені в них символи."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Відстежув. спроби розблок. екрана"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Дозволяє програмі змінювати історію чи закладки веб-переглядача, збережені у вашому телефоні. Це може дозволити програмі стирати чи змінювати дані веб-переглядача. Зауважте: цей дозвіл не може застосовуватися веб-переглядачами третіх сторін або іншими програмами з можливостями веб-перегляду."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"установлювати будильник"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Дозволяє програмі налаштовувати сигнал у встановленій програмі будильника. У деяких програмах будильника ця функція може не застосовуватися."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"видаляти голосові повідомлення"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Додаток може видаляти голосові повідомлення з папки \"Вхідні\"."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"додавати голосову пошту"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Дозволяє програмі додавати повідомлення в папку \"Вхідні\" голосової пошти."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"читати всі голосові повідомлення"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Додаток може читати всі голосові повідомлення."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"змінювати дозволи географічного місцезнаходження у веб-переглядачі"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Дозволяє програмі змінювати дозволи географічного місцезнаходження у веб-переглядачі. Шкідливі програми можуть використовувати це, щоб дозволяти надсилати інформацію про місцезнаходження довільним веб-сайтам."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"перевіряти пакети"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Дозволяє додатку відстежувати зміни в стані довіри."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Призначення довірчого агента."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Додаток може призначати довірчого агента."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Запустити меню налаштувань довірчого агента"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Додаток може запускати функцію, яка змінює поведінку довірчого агента."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Прив’язуватися до служби довірчих агентів"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Дозволяє додатку прив’язуватися до служби довірчих агентів."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Взаємодіяти з оновленнями системи та системою відновлення."</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Виберіть рік"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Вибрано: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> видалено"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Робоча <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Використовувати блокування в додатку?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Блокування в додатку блокує дисплей у певному додатку.\n\nЩоб вимкнути, натисніть і втримуйте кнопку останніх додатків ($)"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"НІ"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"УВІМКНУТИ"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Увімкнути блокування в додатку"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Вимкнути блокування в додатку"</string>
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 11b7519..56fd457 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Chế độ trên máy bay BẬT"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Chế độ trên máy bay TẮT"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Cài đặt"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của hình nền. Không cần thiết cho các ứng dụng thông thường."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"liên kết với trình tương tác bằng giọng nói"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của dịch vụ tương tác bằng giọng nói. Không cần thiết cho các ứng dụng thông thường."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"quản lý cụm từ khóa bằng giọng nói"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Cho phép chủ sở hữu quản lý các cụm từ khóa để phát hiện từ nóng bằng giọng nói. Không bao giờ cần cho ứng dụng thông thường."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"liên kết với màn hình từ xa"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của màn hình từ xa. Không cần thiết cho các ứng dụng thông thường."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"liên kết với dịch vụ tiện ích con"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Cho phép ứng dụng sửa đổi các thông số hiệu chỉnh của màn hình cảm ứng. Không cần cho ứng dụng thông thường."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"truy cập chứng chỉ DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Cho phép ứng dụng cung cấp và sử dụng chứng chỉ DRM. Không cần thiết cho các ứng dụng thông thường."</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Đặt quy tắc mật khẩu"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kiểm soát độ dài và ký tự được phép trong mật khẩu mở khóa màn hình."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Giám sát những lần thử mở khóa màn hình"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Cho phép ứng dụng sửa đổi lịch sử hoặc dấu trang của Trình duyệt được lưu trữ trên điện thoại của bạn. Việc này có thể cho phép ứng dụng xóa hoặc sửa đổi dữ liệu của Trình duyệt. Lưu ý: quyền này có thể không được thực thi bởi các trình duyệt của bên thứ ba hoặc các ứng dụng khác có khả năng duyệt web."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"đặt báo thức"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Cho phép ứng dụng đặt báo thức trong ứng dụng đồng hồ báo thức được cài đặt. Một số ứng dụng đồng hồ báo thức có thể không thực thi tính  năng này."</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"thêm thư thoại"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Cho phép ứng dụng thêm thông báo vào hộp thư thoại đến của bạn."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"đọc tất cả các thư thoại"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Cho phép ứng dụng này đọc tất cả các thư thoại của bạn."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"sửa đổi các quyền về vị trí địa lý của Trình duyệt"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Cho phép ứng dụng sửa đổi cấp phép vị trí địa lý của Trình duyệt. Ứng dụng độc hại có thể lợi dụng quyền này để cho phép gửi thông tin vị trí tới các trang web tùy ý."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"xác minh gói"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Cho phép ứng dụng quan sát các thay đổi ở trạng thái đáng tin cậy."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Cung cấp tác nhân đáng tin cậy."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Cho phép ứng dụng cung cấp tác nhân đáng tin cậy."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Chạy menu cài đặt đại lý đáng tin cậy."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Cho phép ứng dụng chạy hoạt động thay đổi hoạt động của đại lý đáng tin cậy."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Liên kết với một dịch vụ của đại lý đáng tin cậy"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Cho phép ứng dụng liên kết với một dịch vụ của đại lý đáng tin cậy."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Tương tác với hệ thống khôi phục và bản cập nhật"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"Chọn năm"</string>
     <string name="item_is_selected" msgid="949687401682476608">"Đã chọn <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"Đã xóa <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> làm việc"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Sử dụng lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app sẽ khóa màn hình trong một ứng dụng.\n\nĐể thoát, nhấn và giữ nút ứng dụng gần đây $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"KHÔNG"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"BẮT ĐẦU"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Bắt đầu Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Thoát Lock-to-app"</string>
 </resources>
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 55ecc3a..ba2cdb7 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"已开启飞行模式"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"未开启飞行模式"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"设置"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允许用户绑定到壁纸的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"绑定到语音互动器"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允许应用绑定到语音互动服务的顶级接口。普通应用绝不需要此权限。"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理语音关键短语"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允许应用管理用于语音启动指令检测的关键短语。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"绑定至远程显示屏"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允许应用绑定至远程显示屏的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"绑定到小部件服务"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"允许应用修改触摸屏的校准参数。普通应用绝不需要此权限。"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"访问DRM证书"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"允许应用配置和使用DRM证书。普通应用绝不需要此权限。"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"设置密码规则"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"控制屏幕解锁密码所允许的长度和字符。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"监视屏幕解锁尝试次数"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"允许该应用修改您手机上存储的浏览器历史记录或浏览器书签。此权限可让该应用清除或修改浏览器数据。请注意:此权限可能不适用于第三方浏览器或具备网页浏览功能的其他应用。"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"设置闹钟"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"允许应用在已安装的闹钟应用中设置闹钟。有些闹钟应用可能无法实现此功能。"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"添加语音邮件"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允许应用向您的语音信箱收件箱添加邮件。"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"读取所有语音邮件"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允许应用读取您所有的语音邮件。"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改“浏览器”地理位置的权限"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允许应用修改“浏览器”的地理位置权限。恶意应用可能借此向任意网站发送位置信息。"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"验证软件包"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"允许应用检测信任状态的变化。"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理。"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允许应用提供信任的代理。"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"启动信任的代理设置菜单。"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允许应用启动可变更信任的代理行为的活动。"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"绑定至信任的代理服务"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允许应用绑定至信任的代理服务。"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"与更新和恢复系统互动"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"选择年份"</string>
     <string name="item_is_selected" msgid="949687401682476608">"已选择<xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"已删除<xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"工作<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"要使用“锁定到应用”吗?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"“锁定到应用”功能会锁定屏幕,使其只显示一个应用。\n\n要退出,请按住“最近用过的应用”按钮 $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"算了"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"启动"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"启动“锁定到应用”"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"退出“锁定到应用”"</string>
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index b58a58a..a0e83cf 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"飛航模式為 [開啟]"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"飛行模式為 [關閉]"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"設定"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允許應用程式繫結至桌布的頂層介面 (不建議一般應用程式使用)。"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"繫結至語音互動器"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允許應用程式繫結至語音互動服務的頂層介面,但一般應用程式並不需要使用。"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理語音關鍵字句"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允許應用程式管理語音關鍵字句,用於偵測語音啟動字詞 (一般應用程式並不需要)。"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"繫結至遠端螢幕"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允許應用程式繫結至遠端屏螢的頂層介面 (不建議一般應用程式使用)。"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"繫結至小工具服務"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"允許應用程式修改觸控式螢幕的校正參數,而一般應用程式並不需要作出類似修改。"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"存取 DRM 憑證"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"允許應用程式準備和使用 DRM 憑證,但一般應用程式並不需要使用。"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"設定密碼規則"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"控制屏幕解鎖密碼所允許的長度和字元。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"監控屏幕解鎖嘗試次數"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"允許應用程式修改手機上儲存的瀏覽器記錄或書籤。如此一來,應用程式或可清除或修改瀏覽器資料。注意:這項權限可能不適用於第三方瀏覽器或其他具備網頁瀏覽功能的應用程式。"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"設定鬧鐘"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能沒有這項功能。"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"新增留言"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息加到您的留言信箱收件箱。"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理資訊權限。惡意應用程式可能會藉此允許將您的位置資訊任意傳送給某些網站。"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"允許應用程式聽取信任狀態的變更。"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理程式。"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允許應用程式提供信任的代理程式。"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"啟動信任的代理程式設定選單。"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允許應用程式啟動可變更信任的代理程式行為的活動。"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"繫結至信任的代理程式服務"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允許應用程式繫結至信任的代理程式服務。"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"與更新和復原系統互動"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"選取年份"</string>
     <string name="item_is_selected" msgid="949687401682476608">"已選取<xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 已刪除"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index f7dae2c..659ffe3 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -185,6 +185,8 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"飛航模式為 [開啟]"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"飛航模式為 [關閉]"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"設定"</string>
+    <!-- no translation found for global_action_lockdown (8751542514724332873) -->
+    <skip />
     <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>
@@ -400,6 +402,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允許應用程式繫結至桌布的頂層介面 (一般應用程式不需使用)。"</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"繫結至語音互動器"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"允許應用程式繫結至語音互動服務的頂層介面 (一般應用程式並不需要)。"</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"管理語音關鍵字句"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"允許應用程式管理語音關鍵字句,用於偵測語音啟動字詞 (一般應用程式並不需要)。"</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"繫結至遠端螢幕"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"允許應用程式繫結至遠端螢幕的頂層介面 (一般應用程式不需使用)。"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"繫結至小工具服務"</string>
@@ -532,6 +536,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>
@@ -722,6 +728,10 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"允許應用程式修改觸控螢幕的校正參數 (一般應用程式並不需要)。"</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"存取 DRM 憑證"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"允許應用程式佈建及使用 DRM 憑證 (一般應用程式並不需要)。"</string>
+    <!-- no translation found for permlab_handoverStatus (4558616203830448763) -->
+    <skip />
+    <!-- no translation found for permdesc_handoverStatus (5738446261941364055) -->
+    <skip />
     <string name="policylab_limitPassword" msgid="4497420728857585791">"設定密碼規則"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"控制螢幕解鎖密碼所允許的長度和字元。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"監視螢幕解鎖嘗試次數"</string>
@@ -995,8 +1005,14 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"允許應用程式修改手機上儲存的瀏覽紀錄或書籤。這項設定會讓應用程式具有清除或修改瀏覽資料的權限。注意:這項權限不適用於第三方瀏覽器或其他具備網頁瀏覽功能的應用程式。"</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"設定鬧鐘"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能無法執行這項功能。"</string>
+    <!-- no translation found for permlab_removeVoicemail (6328485960478155867) -->
+    <skip />
+    <!-- no translation found for permdesc_removeVoicemail (8113704917331103065) -->
+    <skip />
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"新增語音留言"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息新增至您的語音信箱收件匣。"</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理位置權限。請注意,惡意應用程式可能利用此功能允許將您的位置資訊任意傳送給某些網站。"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string>
@@ -1358,6 +1374,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"允許應用程式接聽信任狀態變更。"</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"提供信任的代理程式。"</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"允許應用程式提供信任的代理程式。"</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"啟動信任的代理程式設定選單。"</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"允許應用程式啟動可變更信任的代理程式行為的活動。"</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"繫結至信任的代理程式服務"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"允許應用程式繫結至信任的代理程式服務。"</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"與更新和還原系統互動"</string>
@@ -1717,4 +1735,11 @@
     <string name="select_year" msgid="7952052866994196170">"選取年份"</string>
     <string name="item_is_selected" msgid="949687401682476608">"已選取 <xliff:g id="ITEM">%1$s</xliff:g>"</string>
     <string name="deleted_key" msgid="7659477886625566590">"已刪除 <xliff:g id="KEY">%1$s</xliff:g>"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string>
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 7d9e36a..27a4331 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -185,6 +185,7 @@
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Imodi yendiza IVULIWE"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Imodi yendiza IVALIWE"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Izilungiselelo"</string>
+    <string name="global_action_lockdown" msgid="8751542514724332873">"Khiya manje"</string>
     <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>
@@ -400,6 +401,8 @@
     <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"Ivumela umbambi ukuhlanganisa uxhumano nomsebenzisi kwezinga eliphezulu lwephephadonga. Akusoze kwadingeka kwezinhlelo zokusebenza ezivamile."</string>
     <string name="permlab_bindVoiceInteraction" msgid="5334852580713715068">"hlanganisa kusisebenzisani sezwi"</string>
     <string name="permdesc_bindVoiceInteraction" msgid="2345721766501778101">"Ivumela umbambi ukuhlanganisa isixhumi esibonakalayo sesevisi yokusebenzisana yezwi. Akufanele kudingekele izinhlelo zokusebenza ezivamile."</string>
+    <string name="permlab_manageVoiceKeyphrases" msgid="1252285102392793548">"phatha amabinzana angukhiye wezwi"</string>
+    <string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"Ivumela isibambi ukuthi siphathe amabinzana angukhiye wokutholwa kwe-hotword yezwi. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string>
     <string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"bophezela kusibonisi sesilawuli kude"</string>
     <string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"Ivumela umbambi ukuhlanganisa isixhumi esibonakalayo esisezingeni eliphezulu sesibonisi sesilawuli kude. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bophezela kube isevisi yesinqunjana"</string>
@@ -532,6 +535,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>
@@ -722,6 +727,8 @@
     <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"Ivumela uhlelo lokusebenza ukuthi lushintshe imingcele yokulinganisa yesikrini esithintwayo. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string>
     <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"finyelela izitifiketi ze-DRM"</string>
     <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"Ivumela uhlelo lokusebenza ekunikezweni nokusetshenziswa kwezitifiketi ze-DRM. Akufanele kudingeke kuzinhlelo zokusebenza ezivamile."</string>
+    <string name="permlab_handoverStatus" msgid="4558616203830448763">"Thola ukusakaza kokuthuthukisela kokunikeza."</string>
+    <string name="permdesc_handoverStatus" msgid="5738446261941364055">"Ivumela ukuthola ulwazi lwesimo sokuthutha olunikezwayo."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Misa imithetho yephasiwedi"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Lawula ubude nezinhlamvu ezivunyelwe kumaphasiwedi okuvula isikrini"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Gaka imizamo yokuvula isikrini"</string>
@@ -995,8 +1002,12 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Ivumela uhlelo lokusebenza ukushintsha umlando wamabhukhimakhi noma wesiphequluli alondolozwe efonini yakho. Lokhu kungavumela uhlelo lokusebenza ukususa noma ukushintsha idatha yesiphequluli. Qaphela: le mvume kungenzeka ingaphoqelelwa iziphequluli ezivela eceleni noma ezinye izinhlelo zokusebenza ezinamandla okuphequlula iwebhu."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"setha i-alamu"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Ivumela uhlelo lokusebenza ukuthi isethe i-alamu ensizeni efkiwe ye-alamu. Ezinye izinhlelo zokusebenza ze-alamu kungenzeka zingakusebenzisi lokho."</string>
+    <string name="permlab_removeVoicemail" msgid="6328485960478155867">"susa amavoyisimeyili"</string>
+    <string name="permdesc_removeVoicemail" msgid="8113704917331103065">"Ivumela uhlelo lokusebenza ukususa imilayezo kusuka kubhokisi lokungenayo levoyisimeyili."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"engeza imeyili yezwi"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ivumela uhlelo lokusebenza ukwengeza imiyalezo kwibhokisi lakho lemeyili yezwi."</string>
+    <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"funda wonke amavoyisimeyili"</string>
+    <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ivumela uhlelo lokusebenza ukuthi lufunde wonke amavoyisimeyili wakho."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Gugula izimvume zendawo Yesiphequluli"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ivumela uhlelo lokusebenza ukuthi iguqule izimvume eziphathelene nezindawo Zesiphequluli. Izuhlelo lokusebenza eziyingozi zingasebenzisa lokhu ukuvumela ukuvumela imininingwane yendawo kunoma imaphi amawebusayithi."</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"qinisekisa amaphakheji"</string>
@@ -1358,6 +1369,8 @@
     <string name="permdesc_trust_listener" msgid="8233895334214716864">"Ivumela uhlelo lokusebenza ukuthi lilalelele izinguquko kusimo sethemba."</string>
     <string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Nikeza umsebenzeli owethembekile."</string>
     <string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Ivumela uhlelo lokusebenza ukunikeza umsebenzeli owethembekile."</string>
+    <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Qalisa imenyu yezilungiselelo zomsebenzeli wethemba."</string>
+    <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Ivumela uhlelo lokusebenza ukuthi luqalise umsebenzi oguqula ukuziphatha komsebenzeli wethemba."</string>
     <string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Bophezela kusevisi yomenzeli wethemba"</string>
     <string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Ivumela uhlelo lokusebenza ukuthi libophezeleke kusevisi yomenzeli wethemba."</string>
     <string name="permlab_recovery" msgid="3157024487744125846">"Ixhumana nesibuyekezo nesistimu yokutakula"</string>
@@ -1717,4 +1730,11 @@
     <string name="select_year" msgid="7952052866994196170">"Khetha unyaka"</string>
     <string name="item_is_selected" msgid="949687401682476608">"I-<xliff:g id="ITEM">%1$s</xliff:g> ekhethiwe"</string>
     <string name="deleted_key" msgid="7659477886625566590">"I-<xliff:g id="KEY">%1$s</xliff:g> isusiwe"</string>
+    <string name="managed_profile_label_badge" msgid="2355652472854327647">"Umsebenzi <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="lock_to_app_title" msgid="5895142291937470019">"Sebenzisa isikhiya sohlelo lokusebenza?"</string>
+    <string name="lock_to_app_description" msgid="8597199033462406175">"Isikhiya kuhlelo lokusebenza sikhiyela isibonisi kuhlelo lokusebenza olulodwa.\n\nUkuze uphume cindezela uphinde ubambe inkinobho yezinhlelo zokusebenza zakamuva engu-$"</string>
+    <string name="lock_to_app_negative" msgid="8522854387366288195">"CHA"</string>
+    <string name="lock_to_app_positive" msgid="7085139175671313864">"QALA"</string>
+    <string name="lock_to_app_start" msgid="8889002974248178076">"Qala Isikhiya sohlelo lokusebenza"</string>
+    <string name="lock_to_app_exit" msgid="7033017307788432861">"Phuma kusikhiya sohlelo lokusebenza"</string>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ed648fb..e7e750d 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 -->
         <!-- ============ -->
@@ -828,6 +834,8 @@
         <attr name="preferenceFragmentPaddingSide" format="dimension" />
         <!-- Default style for switch preferences. -->
         <attr name="switchPreferenceStyle" format="reference" />
+        <!-- Default style for seekbar preferences. -->
+        <attr name="seekBarPreferenceStyle" format="reference" />
 
         <!-- ============================ -->
         <!-- Text selection handle styles -->
@@ -1813,6 +1821,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. -->
@@ -2411,7 +2424,7 @@
 
         <!-- Names a View such that it can be identified for Transitions. Names should be
              unique in the View hierarchy. -->
-        <attr name="viewName" format="string" />
+        <attr name="transitionName" format="string" />
 
         <!-- Specifies that this view should permit nested scrolling within a compatible
              ancestor view. -->
@@ -2419,6 +2432,30 @@
 
         <!-- Sets the state-based animator for the View. -->
         <attr name="stateListAnimator" format="reference"/>
+
+        <!-- Tint to apply to the background. -->
+        <attr name="backgroundTint" format="color" />
+
+        <!-- Blending mode used to apply the background tint. -->
+        <attr name="backgroundTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -3008,7 +3045,29 @@
         <!-- Indicates the initial checked state of this button. -->
         <attr name="checked" format="boolean" />
         <!-- Drawable used for the button graphic (e.g. checkbox, radio button, etc). -->
-        <attr name="button" format="reference"/>
+        <attr name="button" format="reference" />
+        <!-- Tint to apply to the button graphic. -->
+        <attr name="buttonTint" format="color" />
+        <!-- Blending mode used to apply the button graphic tint. -->
+        <attr name="buttonTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
     </declare-styleable>
     <declare-styleable name="CheckedTextView">
         <!-- Indicates the initial checked state of this text. -->
@@ -3093,6 +3152,28 @@
         <!-- Determines whether to measure all children or just those in
              the VISIBLE or INVISIBLE state when measuring. Defaults to false. -->
         <attr name="measureAllChildren" format="boolean" />
+        <!-- Tint to apply to the foreground. -->
+        <attr name="foregroundTint" format="color" />
+        <!-- Blending mode used to apply the foreground tint. -->
+        <attr name="foregroundTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
     </declare-styleable>
     <declare-styleable name="ExpandableListView">
         <!-- Indicator shown beside the group View. This can be a stateful Drawable. -->
@@ -3198,6 +3279,10 @@
         <!-- @hide The alpha value (0-255) set on the ImageView's drawable. Equivalent
              to calling ImageView.setAlpha(int), not the same as View.setAlpha(float). -->
         <attr name="drawableAlpha" format="integer" />
+        <!-- Tint to apply to the image. -->
+        <attr name="tint" />
+        <!-- Blending mode used to apply the image tint. -->
+        <attr name="tintMode" />
     </declare-styleable>
     <declare-styleable name="ToggleButton">
         <!-- The text for the button when it is checked. -->
@@ -3388,6 +3473,98 @@
         <!-- Defines if the associated drawables need to be mirrored when in RTL mode.
              Default is false -->
         <attr name="mirrorForRtl" format="boolean" />
+        <!-- Tint to apply to the progress indicator. -->
+        <attr name="progressTint" format="color" />
+        <!-- Blending mode used to apply the progress indicator tint. -->
+        <attr name="progressTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
+        <!-- Tint to apply to the progress indicator background. -->
+        <attr name="progressBackgroundTint" format="color" />
+        <!-- Blending mode used to apply the progress indicator background tint. -->
+        <attr name="progressBackgroundTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
+        <!-- Tint to apply to the secondary progress indicator. -->
+        <attr name="secondaryProgressTint" format="color" />
+        <!-- Blending mode used to apply the secondary progress indicator tint. -->
+        <attr name="secondaryProgressTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
+        <!-- Tint to apply to the indepterminate progress indicator. -->
+        <attr name="indeterminateTint" format="color" />
+        <!-- Blending mode used to apply the indeterminate progress indicator tint. -->
+        <attr name="indeterminateTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
+        <!-- Tint to apply to the background. -->
+        <attr name="backgroundTint" />
+        <!-- Blending mode used to apply the background tint. -->
+        <attr name="backgroundTintMode" />
     </declare-styleable>
 
     <declare-styleable name="SeekBar">
@@ -3397,6 +3574,28 @@
         <attr name="thumbOffset" format="dimension" />
         <!-- Whether to split the track and leave a gap for the thumb drawable. -->
         <attr name="splitTrack" format="boolean" />
+        <!-- Tint to apply to the button graphic. -->
+        <attr name="thumbTint" format="color" />
+        <!-- Blending mode used to apply the button graphic tint. -->
+        <attr name="thumbTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="StackView">
@@ -3859,8 +4058,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 +4265,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 +4276,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)}. -->
@@ -4605,6 +4813,32 @@
                  mirror images so that adjacent images always seam. -->
             <enum name="mirror" value="2" />
         </attr>
+        <!-- Defines the horizontal tile mode. When the tile mode is enabled, the bitmap is repeated.
+             Gravity is ignored when the tile mode is enabled. Default value is "disabled". -->
+        <attr name="tileModeX">
+            <!-- Do not tile the bitmap. This is the default value. -->
+            <enum name="disabled" value="-1" />
+            <!-- Replicates the edge color. -->
+            <enum name="clamp" value="0" />
+            <!-- Repeats the bitmap in both direction. -->
+            <enum name="repeat" value="1" />
+            <!-- Repeats the shader's image horizontally and vertically, alternating
+                 mirror images so that adjacent images always seam. -->
+            <enum name="mirror" value="2" />
+        </attr>
+        <!-- Defines the vertical tile mode. When the tile mode is enabled, the bitmap is repeated.
+             Gravity is ignored when the tile mode is enabled. Default value is "disabled". -->
+        <attr name="tileModeY">
+            <!-- Do not tile the bitmap. This is the default value. -->
+            <enum name="disabled" value="-1" />
+            <!-- Replicates the edge color. -->
+            <enum name="clamp" value="0" />
+            <!-- Repeats the bitmap in both direction. -->
+            <enum name="repeat" value="1" />
+            <!-- Repeats the shader's image horizontally and vertically, alternating
+                 mirror images so that adjacent images always seam. -->
+            <enum name="mirror" value="2" />
+        </attr>
         <!-- Enables or disables the mipmap hint. See
             {@link android.graphics.Bitmap#setHasMipMap(boolean)} for more information.
             Default value is false. -->
@@ -4763,13 +4997,29 @@
         <attr name="height" />
         <!-- Enables or disables dithering. -->
         <attr name="dither" />
+        <!-- If set, specifies the color to apply to the drawable as a tint. By default,
+             no tint is applied. May be a color state list. -->
+        <attr name="tint" />
+        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
+             default value is src_in, which treats the drawable as an alpha mask. -->
+        <attr name="tintMode" />
     </declare-styleable>
 
     <!-- ========================== -->
-    <!--   Vector drawable class   -->
+    <!--   VectorDrawable class   -->
     <!-- ========================== -->
     <eat-comment />
 
+    <!-- Drawable used to draw vector paths. -->
+    <declare-styleable name="VectorDrawable">
+        <!-- If set, specifies the color to apply to the drawable as a tint. By default,
+             no tint is applied. May be a color state list. -->
+        <attr name="tint" />
+        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
+             default value is src_in, which treats the drawable as an alpha mask. -->
+        <attr name="tintMode" />
+    </declare-styleable>
+
     <!-- Define the virtual size of the drawing surface paths will draw to. -->
     <declare-styleable name="VectorDrawableViewport">
         <!-- The width of the canvas the drawing is on. -->
@@ -4786,7 +5036,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 +5066,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 +5099,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 />
@@ -5007,10 +5292,18 @@
     </declare-styleable>
 
     <declare-styleable name="PathInterpolator">
+        <!-- The x coordinate of the first control point of the cubic Bezier -->
         <attr name="controlX1" format="float" />
+        <!-- The y coordinate of the first control point of the cubic Bezier -->
         <attr name="controlY1" format="float" />
+        <!-- The x coordinate of the second control point of the cubic Bezier -->
         <attr name="controlX2" format="float" />
+        <!-- The y coordinate of the second control point of the cubic Bezier -->
         <attr name="controlY2" format="float" />
+        <!-- The control points defined as a path.
+             When pathData is defined, then both of the control points of the
+             cubic Bezier will be ignored. -->
+        <attr name="pathData"/>
     </declare-styleable>
 
     <!-- ========================== -->
@@ -5030,10 +5323,10 @@
         <attr name="interpolator" />
         <!-- The match order to use for the transition. This is a comma-separated
              list of values, containing one or more of the following:
-             id, itemId, viewName, instance. These correspond to
+             id, itemId, name, instance. These correspond to
              {@link android.transition.Transition#MATCH_ID},
              {@link android.transition.Transition#MATCH_ITEM_ID},
-             {@link android.transition.Transition#MATCH_VIEW_NAME}, and
+             {@link android.transition.Transition#MATCH_NAME}, and
              {@link android.transition.Transition#MATCH_INSTANCE}, respectively.
              This corresponds to {@link android.transition.Transition#setMatchOrder(int...)}. -->
         <attr name="matchOrder" format="string" />
@@ -5045,6 +5338,8 @@
          resource are available in addition to the specific attributes of Fade
          described here. -->
     <declare-styleable name="Fade">
+        <!-- Equivalent to <code>visibilityMode</code>, fadingMode works only
+             with the Fade transition. -->
         <attr name="fadingMode">
             <!-- Fade will only fade appearing items in. -->
             <enum name="fade_in" value="1" />
@@ -5073,6 +5368,21 @@
         </attr>
     </declare-styleable>
 
+    <!-- Use with {@link android.transition.Visibility} transitions, such as
+         <code>slide</code>, <code>explode</code>, and <code>fade</code> to mark which
+         views are supported. -->
+    <declare-styleable name="VisibilityTransition">
+        <!-- Changes whether the transition supports appearing and/or disappearing Views.
+             Corresponds to {@link android.transition.Visibility#setMode(int)}. -->
+        <attr name="visibilityMode">
+            <!-- Only appearing Views will be supported. -->
+            <enum name="mode_in" value="1" />
+            <!-- Only disappearing Views will be supported. -->
+            <enum name="mode_out" value="2" />
+            <!-- Both appearing and disappearing views will be supported. -->
+            <enum name="mode_in_out" value="3" />
+        </attr>
+    </declare-styleable>
     <!-- Use <code>target</code> as the root tag of the XML resource that
      describes a {@link android.transition.Transition#addTarget(int)
      targetId} of a transition. There can be one or more targets inside
@@ -5088,10 +5398,10 @@
         <attr name="targetClass" />
         <!-- The fully-qualified name of the Class to exclude from this transition. -->
         <attr name="excludeClass" format="string" />
-        <!-- The viewName of the target on which this transition will animation changes. -->
-        <attr name="targetViewName" format="string" />
-        <!-- The viewName of the target to exclude from this transition. -->
-        <attr name="excludeViewName" format="string" />
+        <!-- The transitionName of the target on which this transition will animation changes. -->
+        <attr name="targetName" format="string" />
+        <!-- The transitionName of the target to exclude from this transition. -->
+        <attr name="excludeName" format="string" />
     </declare-styleable>
 
     <!-- Use <code>set</code> as the root tag of the XML resource that
@@ -5137,9 +5447,9 @@
              greater than 0 or infinite. The default value is restart. -->
         <attr name="repeatMode"/>
         <!-- Value the animation starts from. -->
-        <attr name="valueFrom" format="float|integer|color|dimension"/>
+        <attr name="valueFrom" format="float|integer|color|dimension|string"/>
         <!-- Value the animation animates to. -->
-        <attr name="valueTo" format="float|integer|color|dimension"/>
+        <attr name="valueTo" format="float|integer|color|dimension|string"/>
         <!-- The type of valueFrom and valueTo. -->
         <attr name="valueType">
             <!-- valueFrom and valueTo are floats. This is the default value is valueType is
@@ -5149,6 +5459,9 @@
             <enum name="floatType" value="0" />
             <!-- valueFrom and valueTo are integers. -->
             <enum name="intType"   value="1" />
+            <!-- valueFrom and valueTo are paths defined as strings.
+                 This type is used for path morphing in AnimatedVectorDrawable. -->
+            <enum name="pathType"   value="2" />
         </attr>
     </declare-styleable>
 
@@ -5160,6 +5473,12 @@
     <declare-styleable name="PropertyAnimator">
         <!-- Name of the property being animated. -->
         <attr name="propertyName" format="string"/>
+        <!-- Name of the property being animated as the X coordinate of the pathData. -->
+        <attr name="propertyXName" format="string"/>
+        <!-- Name of the property being animated as the Y coordinate of the pathData. -->
+        <attr name="propertyYName" format="string"/>
+        <!-- The path used to animate the properties in the ObjectAnimator -->
+        <attr name="pathData"/>
     </declare-styleable>
 
 
@@ -6126,13 +6445,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 +6687,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. -->
@@ -6477,6 +6809,8 @@
         <attr name="switchPadding" format="dimension" />
         <!-- Whether to split the track and leave a gap for the thumb drawable. -->
         <attr name="splitTrack" />
+        <!-- Whether to draw on/off text. -->
+        <attr name="showText" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="Pointer">
@@ -6539,6 +6873,10 @@
         <attr name="disableDependentsState" />
     </declare-styleable>
 
+    <declare-styleable name="SeekBarPreference">
+        <attr name="layout" />
+    </declare-styleable>
+
     <!-- Use <code>tts-engine</code> as the root tag of the XML resource that
          describes a text to speech engine implemented as a subclass of
          {@link android.speech.tts.TextToSpeechService}.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 814d8fc..fc1d0df 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -814,6 +814,14 @@
          via adb.  The default value of this attribute is <code>true</code>. -->
     <attr name="allowBackup" format="boolean" />
 
+    <!-- Indicates that even though the application provides a <code>BackupAgent</code>,
+         only full-data streaming backup operations are to be performed to save the app's
+         data.  This lets the app rely on full-data backups while still participating in
+         the backup and restore process via the BackupAgent's full-data backup APIs.
+         When this attribute is <code>true</code> the app's BackupAgent overrides of
+         the onBackup() and onRestore() callbacks can be empty stubs. -->
+    <attr name="fullBackupOnly" format="boolean" />
+
     <!-- Whether the application in question should be terminated after its
          settings have been restored during a full-system restore operation.
          Single-package restore operations will never cause the application to
@@ -873,17 +881,33 @@
     <!-- The name of the logical parent of the activity as it appears in the manifest. -->
     <attr name="parentActivityName" format="string" />
 
-    <!-- Define an activity that will persist across reboots. If such an activity is in the
-         Recents list when the device is shut off it will appear in the Recents list when
-         the device is next powered on. To be persisted all activities in the task from the
-         root activity up to the last activity before a <em>break</em> must be declared with
-         the persistable attribute. A <em>break</em> is the first activity after the root
-         started with Intent.FLAG_CLEAR_TASK_WHEN_RESET.
+    <!-- Define how an activity persist across reboots. Activities defined as "never" will not
+         be persisted. Those defined as "always" will be persisted. Those defined as "taskOnly"
+         will persist the root activity of the task only. See below for more detail as to
+         what gets persisted. -->
+    <attr name="persistableMode">
+        <!-- The default. If this activity forms the root of a task then that task will be
+             persisted across reboots but only the launching intent will be used. All
+             activities above this activity in the task will not be persisted. In addition
+             this activity will not be passed a PersistableBundle into which it could have
+             stored its state. -->
+        <enum name="persistRootOnly" value="0" />
+        <!-- If this activity forms the root of a task then that task will not be persisted
+             across reboots -->
+        <enum name="doNotPersist" value="1" />
+        <!-- If this activity forms the root of a task then the task and this activity will
+             be persisted across reboots. If the activity above this activity is also
+             tagged with the attribute <code>"persist"</code> then it will be persisted as well.
+             And so on up the task stack until either an activity without the
+             <code>persistableMode="persistAcrossReboots"</code> attribute or one that was launched
+             with the flag Intent.FLAG_CLEAR_TASK_WHEN_RESET is encountered.
 
-         <p>Activities that are declared with the persistable attribute will be provided with a
-         forced-persistable Bundle in onCreate() and onSavedInstanceState(), and must only
-         be passed a persistable Bundle in their Intent.extras. -->
-    <attr name="persistable" format="boolean" />
+             <p>Activities that are declared with the persistAcrossReboots attribute will be
+             provided with a PersistableBundle in onSavedInstanceState(), These activities may
+             use this PeristableBundle to save their state. Then, following a reboot, that
+             PersistableBundle will be provided back to the activity in its onCreate() method. -->
+        <enum name="persistAcrossReboots" value="2" />
+    </attr>
 
     <!-- This attribute specifies that an activity shall become the root activity of a
          new task each time it is launched. Using this attribute permits the user to
@@ -961,6 +985,14 @@
          Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} -->
     <attr name="autoRemoveFromRecents" format="boolean" />
 
+    <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the
+         next activity in the task. If the next activity also has this attribute set to true then
+         it will yield the baseIntent to any activity that it launches in the same task. This
+         continues until an activity is encountered which has this attribute set to false. False
+         is the default. This attribute set to true also permits activity's use of the
+         TaskDescription to change labels, colors and icons in the recent task list. -->
+    <attr name="relinquishTaskIdentity" format="boolean" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1044,6 +1076,7 @@
         <attr name="testOnly" />
         <attr name="backupAgent" />
         <attr name="allowBackup" />
+        <attr name="fullBackupOnly" />
         <attr name="killAfterRestore" />
         <attr name="restoreNeedsApplication" />
         <attr name="restoreAnyVersion" />
@@ -1623,11 +1656,12 @@
         <!-- @hide This broacast receiver will only receive broadcasts for the
              primary user.  Can only be used with receivers. -->
         <attr name="primaryUserOnly" format="boolean" />
-        <attr name="persistable" />
+        <attr name="persistableMode" />
         <attr name="allowEmbedded" />
         <attr name="documentLaunchMode" />
         <attr name="maxRecents" />
         <attr name="autoRemoveFromRecents" />
+        <attr name="relinquishTaskIdentity" />
     </declare-styleable>
     
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 9f6c7ad..dd316ed 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -125,9 +125,6 @@
 
     <color name="micro_text_light">#434343</color>
 
-    <color name="leanback_dark_gray">#ff333333</color>
-    <color name="leanback_light_gray">#ff888888</color>
-
     <drawable name="notification_template_icon_bg">#3333B5E5</drawable>
     <drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
 
@@ -143,7 +140,7 @@
     <color name="keyguard_avatar_nick_color">#ffffffff</color>
     <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color>
 
-    <color name="accessibility_focus_highlight">#80ffff00</color>
+    <color name="accessibility_focus_highlight">#bf39b500</color>
 
 </resources>
 
diff --git a/core/res/res/values/colors_leanback.xml b/core/res/res/values/colors_leanback.xml
new file mode 100644
index 0000000..dc0c673
--- /dev/null
+++ b/core/res/res/values/colors_leanback.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<!-- Colors specific to Leanback themes. -->
+<resources>
+    <color name="background_leanback_dark">#ff324248</color>
+    <color name="background_leanback_light">#ffeeeeee</color>
+
+    <color name="primary_text_leanback_dark">#cceeeeee</color>
+    <color name="secondary_text_leanback_dark">#99eeeeee</color>
+
+    <color name="primary_text_leanback_light">#cc222222</color>
+    <color name="secondary_text_leanback_light">#99222222</color>
+
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 27ac6c30..708fe67 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -116,8 +116,10 @@
          specified for -large and -xlarge configurations. -->
     <dimen name="config_prefDialogWidth">320dp</dimen>
 
-    <!-- Enables or disables fading edges when marquee is enabled in TextView. -->
-    <bool name="config_ui_enableFadingMarquee">true</bool>
+    <!-- Enables or disables fading edges when marquee is enabled in TextView.
+         Off by default, since the framebuffer readback used to implement the
+         fading edges is prohibitively expensive on most GPUs. -->
+    <bool name="config_ui_enableFadingMarquee">false</bool>
 
     <!-- Whether dialogs should close automatically when the user touches outside
          of them.  This should not normally be modified. -->
@@ -628,6 +630,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>
 
@@ -788,6 +793,19 @@
          config_enableFusedLocationOverlay is false. -->
     <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
 
+    <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be
+         replaced by an app at run-time. When disabled, only the
+         config_hardwareFlpPackageName package will be searched for Hardware Flp,
+         otherwise packages whose signature matches the signatures of
+         config_locationProviderPackageNames will be searched, and the service
+         with the highest version number will be picked. Anyone who wants to
+         disable the overlay mechanism can set it to false.
+         -->
+    <bool name="config_enableHardwareFlpOverlay" translatable="false">true</bool>
+    <!-- Package name providing Hardware Flp. Used only when
+         config_enableHardwareFlpOverlay is false. -->
+    <string name="config_hardwareFlpPackageName" translatable="false">com.android.location.fused</string>
+
     <!-- Whether to enable geocoder overlay which allows geocoder to be replaced
          by an app at run-time. When disabled, only the
          config_geocoderProviderPackageName package will be searched for
@@ -1168,7 +1186,7 @@
     <bool name="config_dreamsActivatedOnDockByDefault">true</bool>
     <!-- If supported and enabled, are dreams activated when asleep and charging? (by default) -->
     <bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
-    <!-- ComponentName of the default dream (Settings.Secure.SCREENSAVER_COMPONENT) -->
+    <!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
     <string name="config_dreamsDefaultComponent">com.google.android.deskclock/com.android.deskclock.Screensaver</string>
 
     <!-- Are we allowed to dream while not plugged in? -->
@@ -1189,8 +1207,7 @@
          try to start this dream if possible.  The dream should typically call startDozing()
          to put the display into a low power state and allow the application processor
          to be suspended.  When the dream ends, the system will go to sleep as usual.
-         Specify the component name (Settings.Secure.SCREENSAVER_COMPONENT) or an
-         empty string if none.
+         Specify the component name or an empty string if none.
 
          Note that doze dreams are not subject to the same start conditions as ordinary dreams.
          Doze dreams will run whenever the power manager is in a dozing state. -->
@@ -1446,6 +1463,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.ConfirmDialog</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts">;com.android.settings;</string>
 
@@ -1550,8 +1571,40 @@
          -->
     <string-array translatable="false" name="config_globalActionsList">
         <item>power</item>
+        <item>lockdown</item>
         <item>bugreport</item>
         <item>users</item>
     </string-array>
 
+    <!-- default telephony hardware configuration for this platform.
+    -->
+    <!-- this string array should be overridden by the device to present a list
+         telephony hardware resource.  this is used by the telephony device controller
+         (TDC) to offer the basic capabilities of the hardware to the telephony
+         framework
+    -->
+    <!-- an array of "[hardware type],[hardware-uuid],[state],[[hardware-type specific]]"
+         with, [[hardware-type specific]] in:
+            - "[[ril-model],[rat],[max-active-voice],[max-active-data],[max-active-standby]]"
+              for 'modem' hardware
+            - "[[associated-modem-uuid]]"
+              for 'sim' hardware.
+         refer to HardwareConfig in com.android.internal.telephony for specific details/values
+         those elements can carry.
+    -->
+    <string-array translatable="false" name="config_telephonyHardware">
+        <!-- modem -->
+        <item>"0,modem,0,0,0,1,1,1"</item>
+        <!-- sim -->
+        <item>"1,sim,0,modem"</item>
+    </string-array>
+
+    <!-- This string array can be overriden to add an additional DRM support for WebView EME. -->
+    <!-- Array of "[keySystemName],[UuidOfMediaDrm]" -->
+    <string-array name="config_keySystemUuidMapping" translatable="false">
+        <!-- Example:
+        <item>"x-com.microsoft.playready,9A04F079-9840-4286-AB92-E65BE0885F95"</item>
+        -->
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index be7e6c1..18e4574 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -15,6 +15,11 @@
 -->
 <resources>
 
+    <!-- Preference fragment padding, sides -->
+    <dimen name="preference_fragment_padding_side_material">0dp</dimen>
+
+    <dimen name="preference_screen_header_padding_side_material">0dp</dimen>
+
     <!-- Default height of an action bar. -->
     <dimen name="action_bar_default_height_material">56dp</dimen>
     <!-- Default padding of an action bar. -->
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 639091e..c64e910 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -84,4 +84,5 @@
   <item type="id" name="current_scene" />
   <item type="id" name="scene_layoutid_cache" />
   <item type="id" name="mask" />
+  <item type="id" name="transitionPosition" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e16082f..3206457 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2115,7 +2115,7 @@
   <public type="attr" name="controlY1" />
   <public type="attr" name="controlX2" />
   <public type="attr" name="controlY2" />
-  <public type="attr" name="viewName" />
+  <public type="attr" name="transitionName" />
   <public type="attr" name="transitionGroup" />
   <public type="attr" name="viewportWidth" />
   <public type="attr" name="viewportHeight" />
@@ -2136,7 +2136,7 @@
   <public type="attr" name="colorControlActivated" />
   <public type="attr" name="colorButtonNormal" />
   <public type="attr" name="colorControlHighlight" />
-  <public type="attr" name="persistable" />
+  <public type="attr" name="persistableMode" />
   <public type="attr" name="titleTextAppearance" />
   <public type="attr" name="subtitleTextAppearance" />
   <public type="attr" name="slideEdge" />
@@ -2168,8 +2168,8 @@
   <public type="attr" name="fromId" />
   <public type="attr" name="reversible" />
   <public type="attr" name="splitTrack" />
-  <public type="attr" name="targetViewName" />
-  <public type="attr" name="excludeViewName" />
+  <public type="attr" name="targetName" />
+  <public type="attr" name="excludeName" />
   <public type="attr" name="matchOrder" />
   <public type="attr" name="windowDrawsSystemBarBackgrounds" />
   <public type="attr" name="statusBarColor"/>
@@ -2179,8 +2179,43 @@
   <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 type="attr" name="progressTint" />
+  <public type="attr" name="progressTintMode" />
+  <public type="attr" name="progressBackgroundTint" />
+  <public type="attr" name="progressBackgroundTintMode" />
+  <public type="attr" name="secondaryProgressTint" />
+  <public type="attr" name="secondaryProgressTintMode" />
+  <public type="attr" name="indeterminateTint" />
+  <public type="attr" name="indeterminateTintMode" />
+  <public type="attr" name="backgroundTint" />
+  <public type="attr" name="backgroundTintMode" />
+  <public type="attr" name="foregroundTint" />
+  <public type="attr" name="foregroundTintMode" />
+  <public type="attr" name="buttonTint" />
+  <public type="attr" name="buttonTintMode" />
+  <public type="attr" name="thumbTint" />
+  <public type="attr" name="thumbTintMode" />
+  <public type="attr" name="fullBackupOnly" />
+  <public type="attr" name="propertyXName" />
+  <public type="attr" name="propertyYName" />
+  <public type="attr" name="relinquishTaskIdentity" />
+  <public type="attr" name="tileModeX" />
+  <public type="attr" name="tileModeY" />
+  <public type="attr" name="actionModeShareDrawable" />
+  <public type="attr" name="actionModeFindDrawable" />
+  <public type="attr" name="actionModeWebSearchDrawable" />
+  <public type="attr" name="visibilityMode" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
@@ -2432,5 +2467,26 @@
   <!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
   <public type="interpolator" name="fast_out_linear_in" />
 
+  <!-- Used for Activity Transitions, this transition indicates that no Transition
+       should be used. -->
   <public type="transition" name="no_transition" id="0x010f0000"/>
+  <!-- A transition that moves and resizes a view -->
+  <public type="transition" name="move"/>
+  <!-- A transition that fades views in and out. -->
+  <public type="transition" name="fade"/>
+  <!-- A transition that moves views in or out of the scene to or from the edges when
+       a view visibility changes. -->
+  <public type="transition" name="explode"/>
+  <!-- A transition that moves views in or out of the scene to or from the bottom edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_bottom"/>
+  <!-- A transition that moves views in or out of the scene to or from the top edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_top"/>
+  <!-- A transition that moves views in or out of the scene to or from the right edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_right"/>
+  <!-- A transition that moves views in or out of the scene to or from the left edge when
+       a view visibility changes. -->
+  <public type="transition" name="slide_left"/>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f76bb5..a3262ed 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -443,6 +443,9 @@
     <!-- label for item that launches settings in phone options dialog [CHAR LIMIT=15]-->
     <string name="global_action_settings">Settings</string>
 
+    <!-- label for item that locks the phone and enforces that it can't be unlocked without entering a credential. [CHAR LIMIT=15] -->
+    <string name="global_action_lockdown">Lock now</string>
+
     <!-- Text to use when the number in a notification info is too large
          (greater than status_bar_notification_info_maxnum, defined in
          values/config.xml) and must be truncated. May need to be localized
@@ -460,7 +463,7 @@
     <string name="user_owner_label">Personal apps</string>
 
     <!-- Label for a corporate profile in the intent forwarding app. -->
-    <string name="managed_profile_label">Android Work</string>
+    <string name="managed_profile_label">Work</string>
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_costMoney">Services that cost you money</string>
@@ -1122,6 +1125,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 +1598,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
@@ -2108,6 +2122,11 @@
     <string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider 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_bindDreamService">bind to a dream service</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_bindDreamService">Allows the holder to bind to the top-level interface of a dream 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_invokeCarrierSetup">invoke the carrier-provided configuration app</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_invokeCarrierSetup">Allows the holder to invoke the carrier-provided configuration app. Should never be needed for normal apps.</string>
@@ -2126,6 +2145,9 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
 
+    <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string>
+    <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
@@ -2952,6 +2974,14 @@
       not implement this feature.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether
+        they want to allow the application to remove voicemails from the user's voicemail
+        inbox. [CHAR LIMIT=NONE] -->
+    <string name="permlab_removeVoicemail">remove voicemails</string>
+    <!-- Description of an application permission, listed so the user can choose whether
+        they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_removeVoicemail">Allows the app to remove messages from your voicemail inbox.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether
         they want to allow the application to do this. [CHAR LIMIT=NONE] -->
     <string name="permlab_addVoicemail">add voicemail</string>
     <!-- Description of an application permission, listed so the user can choose whether
@@ -2960,6 +2990,13 @@
       to your voicemail inbox.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether
+        they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permlab_readAllVoicemail">read all voicemail</string>
+    <!-- Description of an application permission, listed so the user can choose whether
+        they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_readAllVoicemail">Allows the app to read all your voicemails.</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_writeGeolocationPermissions">modify Browser geolocation permissions</string>
     <!-- Description of an application permission, listed so the user can choose whether
@@ -3784,6 +3821,8 @@
     <string name="permlab_provide_trust_agent">Provide a trust agent.</string>
     <!-- Description of an application permission that lets it provide a trust agent. -->
     <string name="permdesc_provide_trust_agent">Allows an application to provide a trust agent.</string>
+    <string name="permlab_launch_trust_agent_settings">Lunch trust agent settings menu.</string>
+    <string name="permdesc_launch_trust_agent_settings">Allows an application to lunch an activity that changes the trust agent behavior.</string>
 
     <!-- Title of an application permission that lets it bind to a trust agent service. -->
     <string name="permlab_bind_trust_agent_service">Bind to a trust agent service</string>
@@ -4708,6 +4747,13 @@
     <!-- Accessibility announcement when a number that had been typed in is deleted [CHAR_LIMIT=NONE] -->
     <string name="deleted_key"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string>
 
+    <!--
+        Used to wrap a label for content description for a managed profile, e.g. "Work Email" instead
+        of email when there are two email apps.
+        [CHAR LIMIT=20]
+     -->
+    <string name="managed_profile_label_badge">Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
+
     <!-- DO NOT TRANSLATE -->
     <string name="time_placeholder">--</string>
 
@@ -4719,4 +4765,17 @@
     <!-- DO NOT TRANSLATE -->
     <string name="day_of_week_label_typeface">sans-serif</string>
 
+    <!-- Lock-to-app dialog title. -->
+    <string name="lock_to_app_title">Use lock-to-app?</string>
+    <!-- Lock-to-app dialog description. The $ is not actually shown or translated, it is a marker of where the recents icon shows up. -->
+    <string name="lock_to_app_description">Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button  $</string>
+    <!-- Lock-to-app negative response. -->
+    <string name="lock_to_app_negative">NO</string>
+    <!-- Lock-to-app positive response. -->
+    <string name="lock_to_app_positive">START</string>
+    <!-- Starting lock-to-app indication. -->
+    <string name="lock_to_app_start">Start Lock-to-app</string>
+    <!-- Exting lock-to-app indication. -->
+    <string name="lock_to_app_exit">Exit Lock-to-app</string>
+
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c769cd9..d6be3133 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>
@@ -988,6 +980,10 @@
         <item name="android:switchTextOff">@android:string/capital_off</item>
     </style>
 
+    <style name="Preference.SeekBarPreference">
+        <item name="android:layout">@android:layout/preference_widget_seekbar</item>
+    </style>
+
     <style name="Preference.PreferenceScreen">
     </style>
 
@@ -1043,6 +1039,10 @@
         <item name="android:switchTextOff">@android:string/capital_off</item>
     </style>
 
+    <style name="Preference.Holo.SeekBarPreference">
+        <item name="android:layout">@android:layout/preference_widget_seekbar</item>
+    </style>
+
     <style name="Preference.Holo.PreferenceScreen">
     </style>
 
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 84dbc79..84d38ce 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -254,6 +254,7 @@
     <style name="Preference.DeviceDefault.PreferenceScreen" parent="Preference.Material.PreferenceScreen"/>
     <style name="Preference.DeviceDefault.RingtonePreference" parent="Preference.Material.RingtonePreference"/>
     <style name="Preference.DeviceDefault.SwitchPreference" parent="Preference.Material.SwitchPreference"/>
+    <style name="Preference.DeviceDefault.SeekBarPreference" parent="Preference.Material.SeekBarPreference"/>
 
     <!-- AlertDialog Styles -->
     <style name="AlertDialog.DeviceDefault" parent="AlertDialog.Material"/>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index a37da4e..72d0379 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -14,46 +14,12 @@
      limitations under the License.
 -->
 <resources>
-    <style name="DialogWindowTitle.Leanback" parent="DialogWindowTitle.Holo">
-        <item name="android:textAppearance">@style/TextAppearance.Leanback.DialogWindowTitle</item>
-    </style>
-
-    <style name="TextAppearance.Leanback.DialogWindowTitle" parent="TextAppearance.Holo.DialogWindowTitle">
-        <item name="android:fontFamily">sans-serif-condensed</item>
-        <item name="android:textColor">?attr/textColorPrimary</item>
-    </style>
-
-    <style name="Leanback.ButtonBar" parent="Holo.ButtonBar">
-        <item name="showDividers">none</item>
-    </style>
-
-    <style name="AlertDialog.Leanback" parent="AlertDialog.Holo">
+    <style name="AlertDialog.Leanback" parent="AlertDialog.Material">
         <item name="layout">@android:layout/alert_dialog_leanback</item>
         <item name="buttonPanelSideLayout">@android:layout/alert_dialog_leanback_button_panel_right</item>
-        <item name="progressLayout">@android:layout/progress_dialog_leanback</item>
-        <item name="fullDark">@android:color/background_dark</item>
-        <item name="topDark">@android:color/background_dark</item>
-        <item name="centerDark">@android:color/background_dark</item>
-        <item name="bottomDark">@android:color/background_dark</item>
-        <item name="fullBright">@android:color/background_dark</item>
-        <item name="topBright">@android:color/background_dark</item>
-        <item name="centerBright">@android:color/background_dark</item>
-        <item name="bottomBright">@android:color/background_dark</item>
-        <item name="bottomMedium">@android:color/background_dark</item>
-        <item name="centerMedium">@android:color/background_dark</item>
     </style>
 
     <style name="AlertDialog.Leanback.Light">
-        <item name="fullDark">@android:color/leanback_light_gray</item>
-        <item name="topDark">@android:color/leanback_light_gray</item>
-        <item name="centerDark">@android:color/leanback_light_gray</item>
-        <item name="bottomDark">@android:color/leanback_light_gray</item>
-        <item name="fullBright">@android:color/leanback_light_gray</item>
-        <item name="topBright">@android:color/leanback_light_gray</item>
-        <item name="centerBright">@android:color/leanback_light_gray</item>
-        <item name="bottomBright">@android:color/leanback_light_gray</item>
-        <item name="bottomMedium">@android:color/leanback_light_gray</item>
-        <item name="centerMedium">@android:color/leanback_light_gray</item>
     </style>
 
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 7120521..75f905c 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -37,8 +37,8 @@
     </style>
 
     <style name="PreferenceFragment.Material">
-        <item name="paddingStart">@dimen/preference_fragment_padding_side</item>
-        <item name="paddingEnd">@dimen/preference_fragment_padding_side</item>
+        <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
+        <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
     </style>
 
     <style name="Preference.Material.Information">
@@ -64,6 +64,10 @@
         <item name="switchTextOff">@string/capital_off</item>
     </style>
 
+    <style name="Preference.Material.SeekBarPreference">
+        <item name="layout">@android:layout/preference_widget_seekbar_material</item>
+    </style>
+
     <style name="Preference.Material.PreferenceScreen"/>
 
     <style name="Preference.Material.DialogPreference">
@@ -86,6 +90,38 @@
         <item name="showDefault">true</item>
     </style>
 
+    <!-- No margins or background by default. Could be different for x-large screens -->
+    <style name="PreferencePanel.Material">
+    </style>
+
+    <!-- The attributes are overridden here because the x-large or large resources may have
+         changed the margins and background in the parent PreferencePanel style. -->
+    <style name="PreferencePanel.Material.Dialog">
+        <item name="layout_marginStart">0dip</item>
+        <item name="layout_marginEnd">0dip</item>
+        <item name="layout_marginTop">0dip</item>
+        <item name="layout_marginBottom">0dip</item>
+        <item name="background">@null</item>
+    </style>
+
+    <style name="PreferenceHeaderPanel.Material">
+        <item name="layout_marginStart">@dimen/preference_screen_side_margin</item>
+        <item name="layout_marginEnd">@dimen/preference_screen_side_margin_negative</item>
+        <item name="paddingTop">@dimen/preference_screen_header_vertical_padding</item>
+        <item name="paddingBottom">@dimen/preference_screen_header_vertical_padding</item>
+    </style>
+
+    <style name="PreferenceHeaderList.Material">
+        <item name="paddingStart">@dimen/preference_screen_header_padding_side_material</item>
+        <item name="paddingEnd">@dimen/preference_screen_header_padding_side_material</item>
+        <item name="scrollbarStyle">@integer/preference_screen_header_scrollbarStyle</item>
+    </style>
+
+    <style name="PreferenceFragmentList.Material">
+        <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
+        <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+    </style>
+
     <!-- Begin Material theme styles -->
 
     <!-- Text styles -->
@@ -457,11 +493,12 @@
         <item name="thumb">@drawable/switch_thumb_material_anim</item>
         <item name="splitTrack">true</item>
         <item name="switchTextAppearance">@style/TextAppearance.Material.Widget.Switch</item>
-        <item name="textOn"></item>
-        <item name="textOff"></item>
+        <item name="textOn">@string/capital_on</item>
+        <item name="textOff">@string/capital_off</item>
         <item name="switchMinWidth">4dip</item>
         <item name="switchPadding">4dip</item>
         <item name="background">?attr/selectableItemBackgroundBorderless</item>
+        <item name="showText">false</item>
     </style>
 
     <style name="Widget.Material.EditText" parent="Widget.EditText"/>
@@ -624,6 +661,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..7442459 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -218,6 +218,7 @@
   <java-symbol type="id" name="pin_error_message" />
   <java-symbol type="id" name="timePickerLayout" />
   <java-symbol type="id" name="profile_icon" />
+  <java-symbol type="id" name="transitionPosition" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
   <java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -603,6 +604,14 @@
   <java-symbol type="string" name="kilobyteShort" />
   <java-symbol type="string" name="last_month" />
   <java-symbol type="string" name="launchBrowserDefault" />
+  <java-symbol type="string" name="lock_to_app_title" />
+  <java-symbol type="string" name="lock_to_app_description" />
+  <java-symbol type="string" name="lock_to_app_negative" />
+  <java-symbol type="string" name="lock_to_app_positive" />
+  <java-symbol type="drawable" name="ic_recent" />
+  <java-symbol type="layout" name="lock_to_app_enter" />
+  <java-symbol type="layout" name="lock_to_app_exit" />
+  <java-symbol type="drawable" name="lock_task_notify_bg" />
   <java-symbol type="string" name="lockscreen_access_pattern_cell_added" />
   <java-symbol type="string" name="lockscreen_access_pattern_cleared" />
   <java-symbol type="string" name="lockscreen_access_pattern_detected" />
@@ -863,6 +872,7 @@
   <java-symbol type="string" name="action_bar_home_description_format" />
   <java-symbol type="string" name="action_bar_home_subtitle_description_format" />
   <java-symbol type="string" name="wireless_display_route_description" />
+  <java-symbol type="string" name="managed_profile_label_badge" />
   <java-symbol type="string" name="mediasize_unknown_portrait" />
   <java-symbol type="string" name="mediasize_unknown_landscape" />
   <java-symbol type="string" name="mediasize_iso_a0" />
@@ -999,6 +1009,8 @@
   <java-symbol type="array" name="config_sameNamedOperatorConsideredRoaming" />
   <java-symbol type="array" name="config_callBarringMMI" />
   <java-symbol type="array" name="config_globalActionsList" />
+  <java-symbol type="array" name="config_telephonyHardware" />
+  <java-symbol type="array" name="config_keySystemUuidMapping" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="indicator_input_error" />
@@ -1122,6 +1134,15 @@
   <java-symbol type="drawable" name="ic_corp_badge" />
   <java-symbol type="drawable" name="ic_corp_icon_badge" />
 
+  <java-symbol type="drawable" name="sim_light_blue" />
+  <java-symbol type="drawable" name="sim_light_green" />
+  <java-symbol type="drawable" name="sim_light_orange" />
+  <java-symbol type="drawable" name="sim_light_purple" />
+  <java-symbol type="drawable" name="sim_dark_blue" />
+  <java-symbol type="drawable" name="sim_dark_green" />
+  <java-symbol type="drawable" name="sim_dark_orange" />
+  <java-symbol type="drawable" name="sim_dark_purple" />
+
   <java-symbol type="layout" name="action_bar_home" />
   <java-symbol type="layout" name="action_bar_title_item" />
   <java-symbol type="layout" name="action_menu_item_layout" />
@@ -1404,6 +1425,7 @@
   <java-symbol type="string" name="global_action_silent_mode_off_status" />
   <java-symbol type="string" name="global_action_silent_mode_on_status" />
   <java-symbol type="string" name="global_action_toggle_silent_mode" />
+  <java-symbol type="string" name="global_action_lockdown" />
   <java-symbol type="string" name="invalidPuk" />
   <java-symbol type="string" name="lockscreen_carrier_default" />
   <java-symbol type="style" name="Animation.LockScreen" />
@@ -1462,6 +1484,7 @@
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+  <java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
   <java-symbol type="bool" name="config_enableGeofenceOverlay" />
   <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -1518,6 +1541,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" />
@@ -1552,6 +1576,7 @@
   <java-symbol type="string" name="chooser_wallpaper" />
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+  <java-symbol type="string" name="config_hardwareFlpPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
   <java-symbol type="string" name="config_geofenceProviderPackageName" />
   <java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -1647,6 +1672,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" />
@@ -1879,5 +1905,6 @@
   <java-symbol type="color" name="timepicker_default_selector_color_material" />
   <java-symbol type="color" name="timepicker_default_numbers_background_color_material" />
   <java-symbol type="style" name="TextAppearance.Material.TimePicker.TimeLabel" />
+  <java-symbol type="attr" name="seekBarPreferenceStyle" />
 
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index cb5cb0c..5eec197 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -321,6 +321,7 @@
         <item name="preferenceInformationStyle">@android:style/Preference.Information</item>
         <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@android:style/Preference.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@android:style/Preference.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item>
         <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item>
@@ -1203,6 +1204,7 @@
         <item name="preferenceInformationStyle">@android:style/Preference.Holo.Information</item>
         <item name="checkBoxPreferenceStyle">@android:style/Preference.Holo.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@android:style/Preference.Holo.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@android:style/Preference.Holo.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@android:style/Preference.Holo.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@android:style/Preference.Holo.DialogPreference</item>
         <item name="editTextPreferenceStyle">@android:style/Preference.Holo.DialogPreference.EditTextPreference</item>
@@ -1543,6 +1545,7 @@
         <item name="preferenceInformationStyle">@android:style/Preference.Holo.Information</item>
         <item name="checkBoxPreferenceStyle">@android:style/Preference.Holo.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@android:style/Preference.Holo.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@android:style/Preference.Holo.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@android:style/Preference.Holo.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@android:style/Preference.Holo.DialogPreference</item>
         <item name="editTextPreferenceStyle">@android:style/Preference.Holo.DialogPreference.EditTextPreference</item>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 27c8754..8e83e48 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -160,6 +160,7 @@
         <item name="preferenceInformationStyle">@style/Preference.DeviceDefault.Information</item>
         <item name="checkBoxPreferenceStyle">@style/Preference.DeviceDefault.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@style/Preference.DeviceDefault.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.DeviceDefault.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.EditTextPreference</item>
@@ -421,6 +422,7 @@
         <item name="preferenceInformationStyle">@style/Preference.DeviceDefault.Information</item>
         <item name="checkBoxPreferenceStyle">@style/Preference.DeviceDefault.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@style/Preference.DeviceDefault.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.DeviceDefault.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.DeviceDefault.DialogPreference.EditTextPreference</item>
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index eba8dec..a571b98 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -14,26 +14,38 @@
      limitations under the License.
 -->
 <resources>
-    <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Holo.Dialog.BaseAlert">
-      <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item>
+    <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
+      <item name="colorBackground">@color/background_leanback_dark</item>
+      <item name="textColorPrimary">@color/primary_text_leanback_dark</item>
+      <item name="textColorSecondary">@color/secondary_text_leanback_dark</item>
       <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
-      <item name="buttonBarStyle">@style/Leanback.ButtonBar</item>
-      <item name="listDividerAlertDialog">@null</item>
     </style>
 
-    <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Holo.Light.Dialog.BaseAlert">
-      <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item>
+    <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert">
+      <item name="colorBackground">@color/background_leanback_light</item>
+      <item name="textColorPrimary">@color/primary_text_leanback_light</item>
+      <item name="textColorSecondary">@color/secondary_text_leanback_light</item>
       <item name="alertDialogStyle">@style/AlertDialog.Leanback.Light</item>
-      <item name="buttonBarStyle">@style/Leanback.ButtonBar</item>
-      <item name="listDividerAlertDialog">@null</item>
     </style>
 
-    <style name="Theme.Leanback.Light.Dialog" parent="Theme.Holo.Light.Dialog">
-        <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item>
+    <style name="Theme.Leanback.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker">
+      <item name="colorBackground">@color/background_leanback_dark</item>
+      <item name="textColorPrimary">@color/primary_text_leanback_dark</item>
+      <item name="textColorSecondary">@color/secondary_text_leanback_dark</item>
+      <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
     </style>
 
-    <style name="Theme.Leanback.Dialog" parent="Theme.Holo.Dialog">
-        <item name="windowTitleStyle">@style/DialogWindowTitle.Leanback</item>
+    <style name="Theme.Leanback.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker">
+      <item name="colorBackground">@color/background_leanback_light</item>
+      <item name="textColorPrimary">@color/primary_text_leanback_light</item>
+      <item name="textColorSecondary">@color/secondary_text_leanback_light</item>
+      <item name="alertDialogStyle">@style/AlertDialog.Leanback.Light</item>
+    </style>
+
+    <style name="Theme.Leanback.Light.Dialog" parent="Theme.Material.Light.Dialog">
+    </style>
+
+    <style name="Theme.Leanback.Dialog" parent="Theme.Material.Dialog">
     </style>
 
     <style name="Theme.Leanback.Dialog.AppError" parent="Theme.Leanback.Dialog">
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 97ad7e4..5d9fc66 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -156,7 +156,7 @@
         <item name="windowTitleStyle">@style/WindowTitle.Material</item>
         <item name="windowTitleSize">@dimen/action_bar_default_height_material</item>
         <item name="windowTitleBackgroundStyle">@style/WindowTitleBackground.Material</item>
-        <item name="windowContentTransitions">false</item>
+        <item name="windowContentTransitions">true</item>
         <item name="windowAnimationStyle">@style/Animation.Material.Activity</item>
         <item name="windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
         <item name="windowActionBar">true</item>
@@ -277,17 +277,22 @@
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@style/Preference.Material.PreferenceScreen</item>
         <item name="preferenceFragmentStyle">@style/PreferenceFragment.Material</item>
-        <item name="preferenceFragmentPaddingSide">0dip</item>
         <item name="preferenceCategoryStyle">@style/Preference.Material.Category</item>
         <item name="preferenceStyle">@style/Preference.Material</item>
         <item name="preferenceInformationStyle">@style/Preference.Material.Information</item>
         <item name="checkBoxPreferenceStyle">@style/Preference.Material.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@style/Preference.Material.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.Material.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@style/Preference.Material.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.Material.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.Material.DialogPreference.EditTextPreference</item>
         <item name="ringtonePreferenceStyle">@style/Preference.Material.RingtonePreference</item>
         <item name="preferenceLayoutChild">@layout/preference_child_material</item>
+        <item name="preferencePanelStyle">@style/PreferencePanel.Material</item>
+        <item name="preferenceHeaderPanelStyle">@style/PreferenceHeaderPanel.Material</item>
+        <item name="preferenceListStyle">@style/PreferenceHeaderList.Material</item>
+        <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
+        <item name="preferenceFragmentPaddingSide">@dimen/preference_fragment_padding_side_material</item>
         <item name="detailsElementBackground">?attr/colorBackground</item>
 
         <!-- Search widget styles -->
@@ -516,6 +521,7 @@
         <item name="windowEnterTransition">@transition/fade</item>
         <item name="windowSharedElementEnterTransition">@transition/move</item>
         <item name="windowSharedElementExitTransition">@transition/move</item>
+        <item name="windowContentTransitions">true</item>
 
         <!-- Dialog attributes -->
         <item name="dialogTheme">@style/Theme.Material.Light.Dialog</item>
@@ -625,17 +631,22 @@
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@style/Preference.Material.PreferenceScreen</item>
         <item name="preferenceFragmentStyle">@style/PreferenceFragment.Material</item>
-        <item name="preferenceFragmentPaddingSide">0dip</item>
         <item name="preferenceCategoryStyle">@style/Preference.Material.Category</item>
         <item name="preferenceStyle">@style/Preference.Material</item>
         <item name="preferenceInformationStyle">@style/Preference.Material.Information</item>
         <item name="checkBoxPreferenceStyle">@style/Preference.Material.CheckBoxPreference</item>
         <item name="switchPreferenceStyle">@style/Preference.Material.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.Material.SeekBarPreference</item>
         <item name="yesNoPreferenceStyle">@style/Preference.Material.DialogPreference.YesNoPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.Material.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.Material.DialogPreference.EditTextPreference</item>
         <item name="ringtonePreferenceStyle">@style/Preference.Material.RingtonePreference</item>
         <item name="preferenceLayoutChild">@layout/preference_child_material</item>
+        <item name="preferencePanelStyle">@style/PreferencePanel.Material</item>
+        <item name="preferenceHeaderPanelStyle">@style/PreferenceHeaderPanel.Material</item>
+        <item name="preferenceListStyle">@style/PreferenceHeaderList.Material</item>
+        <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
+        <item name="preferenceFragmentPaddingSide">@dimen/preference_fragment_padding_side_material</item>
         <item name="detailsElementBackground">?attr/colorBackground</item>
 
         <!-- PreferenceFrameLayout attributes -->
@@ -1082,26 +1093,30 @@
         <item name="windowCloseOnTouchOutside">false</item>
     </style>
 
-    <!-- Material theme for alert dialog windows, which is used by the
-         {@link android.app.AlertDialog} class.  This is basically a dialog
-         but sets the background to empty so it can do two-tone backgrounds.
-         For applications targeting Honeycomb or newer, this is the default
-         AlertDialog theme. -->
-    <style name="Theme.Material.Dialog.Alert">
+    <style name="Theme.Material.Dialog.BaseAlert">
         <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
         <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
         <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
     </style>
 
-    <!-- Material theme for the TimePicker dialog windows, which is used by the
-         {@link android.app.TimePickerDialog} class. -->
-    <style name="Theme.Material.Dialog.TimePicker">
+    <!-- Material theme for alert dialog windows, which is used by the
+         {@link android.app.AlertDialog} class.  This is basically a dialog
+         but sets the background to empty so it can do two-tone backgrounds.
+         For applications targeting Honeycomb or newer, this is the default
+         AlertDialog theme. -->
+    <style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert"/>
+
+    <style name="Theme.Material.Dialog.BaseTimePicker">
         <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
         <item name="windowContentOverlay">@null</item>
     </style>
 
+    <!-- Material theme for the TimePicker dialog windows, which is used by the
+         {@link android.app.TimePickerDialog} class. -->
+    <style name="Theme.Material.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker"/>
+
     <!-- Theme for a window that will be displayed either full-screen on
          smaller screens (small, normal) or as a dialog on larger screens
          (large, xlarge). -->
@@ -1196,25 +1211,29 @@
          (large, xlarge). -->
     <style name="Theme.Material.Light.DialogWhenLarge.NoActionBar" parent="@style/Theme.Material.Light.NoActionBar" />
 
-    <!-- Material light theme for alert dialog windows, which is used by the
-         {@link android.app.AlertDialog} class.  This is basically a dialog
-         but sets the background to empty so it can do two-tone backgrounds.
-         For applications targeting Honeycomb or newer, this is the default
-         AlertDialog theme. -->
-    <style name="Theme.Material.Light.Dialog.Alert">
+    <style name="Theme.Material.Light.Dialog.BaseAlert">
         <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
         <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
         <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
     </style>
 
-    <!-- Material Light theme for the TimePicker dialog windows, which is used by the
-         {@link android.app.TimePickerDialog} class. -->
-    <style name="Theme.Material.Light.Dialog.TimePicker">
+    <!-- Material light theme for alert dialog windows, which is used by the
+         {@link android.app.AlertDialog} class.  This is basically a dialog
+         but sets the background to empty so it can do two-tone backgrounds.
+         For applications targeting Honeycomb or newer, this is the default
+         AlertDialog theme. -->
+    <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert"/>
+
+    <style name="Theme.Material.Light.Dialog.BaseTimePicker">
         <item name="windowBackground">@color/transparent</item>
         <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
     </style>
 
+    <!-- Material Light theme for the TimePicker dialog windows, which is used by the
+         {@link android.app.TimePickerDialog} class. -->
+    <style name="Theme.Material.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker"/>
+
     <!-- Theme for a presentation window on a secondary display. -->
     <style name="Theme.Material.Light.Dialog.Presentation" parent="@style/Theme.Material.Light.NoActionBar.Fullscreen" />
 
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/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 07a6a10..7f41ac1c 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -29,6 +29,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.net.Uri;
@@ -60,7 +61,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -303,14 +303,11 @@
         return Uri.fromFile(outFile);
     }
 
-    private PackageParser.Package parsePackage(Uri packageURI) {
+    private PackageParser.Package parsePackage(Uri packageURI) throws PackageParserException {
         final String archiveFilePath = packageURI.getPath();
-        PackageParser packageParser = new PackageParser(archiveFilePath);
+        PackageParser packageParser = new PackageParser();
         File sourceFile = new File(archiveFilePath);
-        DisplayMetrics metrics = new DisplayMetrics();
-        metrics.setToDefaults();
-        PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath,
-                metrics, 0);
+        PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, 0);
         packageParser = null;
         return pkg;
     }
@@ -579,18 +576,18 @@
 
         PackageParser.Package pkg;
 
-        InstallParams(String outFileName, int rawResId) {
+        InstallParams(String outFileName, int rawResId) throws PackageParserException {
             this.pkg = getParsedPackage(outFileName, rawResId);
-            this.packageURI = Uri.fromFile(new File(pkg.mScanPath));
+            this.packageURI = Uri.fromFile(new File(pkg.codePath));
         }
 
         InstallParams(PackageParser.Package pkg) {
-            this.packageURI = Uri.fromFile(new File(pkg.mScanPath));
+            this.packageURI = Uri.fromFile(new File(pkg.codePath));
             this.pkg = pkg;
         }
 
         long getApkSize() {
-            File file = new File(pkg.mScanPath);
+            File file = new File(pkg.codePath);
             return file.length();
         }
     }
@@ -691,7 +688,8 @@
         }
     }
 
-    private PackageParser.Package getParsedPackage(String outFileName, int rawResId) {
+    private PackageParser.Package getParsedPackage(String outFileName, int rawResId)
+            throws PackageParserException {
         PackageManager pm = mContext.getPackageManager();
         File filesDir = mContext.getFilesDir();
         File outFile = new File(filesDir, outFileName);
@@ -1343,7 +1341,7 @@
                 assertUninstalled(info);
             }
         } finally {
-            File outFile = new File(ip.pkg.mScanPath);
+            File outFile = new File(ip.pkg.codePath);
             if (outFile != null && outFile.exists()) {
                 outFile.delete();
             }
diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java
new file mode 100644
index 0000000..cf278fb
--- /dev/null
+++ b/core/tests/coretests/src/android/net/IpPrefixTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.net;
+
+import android.net.IpPrefix;
+import android.os.Parcel;
+import static android.test.MoreAsserts.assertNotEqual;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static org.junit.Assert.assertArrayEquals;
+import java.net.InetAddress;
+import java.util.Random;
+import junit.framework.TestCase;
+
+
+public class IpPrefixTest extends TestCase {
+
+    // Explicitly cast everything to byte because "error: possible loss of precision".
+    private static final byte[] IPV4_BYTES = { (byte) 192, (byte) 0, (byte) 2, (byte) 4};
+    private static final byte[] IPV6_BYTES = {
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
+        (byte) 0x0f, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xa0
+    };
+
+    @SmallTest
+    public void testConstructor() {
+        IpPrefix p;
+        try {
+            p = new IpPrefix((byte[]) null, 9);
+            fail("Expected NullPointerException: null byte array");
+        } catch(RuntimeException expected) {}
+
+        try {
+            p = new IpPrefix((InetAddress) null, 10);
+            fail("Expected NullPointerException: null InetAddress");
+        } catch(RuntimeException expected) {}
+
+        try {
+            p = new IpPrefix((String) null);
+            fail("Expected NullPointerException: null String");
+        } catch(RuntimeException expected) {}
+
+
+        try {
+            byte[] b2 = {1, 2, 3, 4, 5};
+            p = new IpPrefix(b2, 29);
+            fail("Expected IllegalArgumentException: invalid array length");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("1.2.3.4");
+            fail("Expected IllegalArgumentException: no prefix length");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("1.2.3.4/");
+            fail("Expected IllegalArgumentException: empty prefix length");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("foo/32");
+            fail("Expected IllegalArgumentException: invalid address");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("1/32");
+            fail("Expected IllegalArgumentException: deprecated IPv4 format");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("1.2.3.256/32");
+            fail("Expected IllegalArgumentException: invalid IPv4 address");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("foo/32");
+            fail("Expected IllegalArgumentException: non-address");
+        } catch(IllegalArgumentException expected) {}
+
+        try {
+            p = new IpPrefix("f00:::/32");
+            fail("Expected IllegalArgumentException: invalid IPv6 address");
+        } catch(IllegalArgumentException expected) {}
+    }
+
+    public void testTruncation() {
+        IpPrefix p;
+
+        p = new IpPrefix(IPV4_BYTES, 32);
+        assertEquals("192.0.2.4/32", p.toString());
+
+        p = new IpPrefix(IPV4_BYTES, 29);
+        assertEquals("192.0.2.0/29", p.toString());
+
+        p = new IpPrefix(IPV4_BYTES, 8);
+        assertEquals("192.0.0.0/8", p.toString());
+
+        p = new IpPrefix(IPV4_BYTES, 0);
+        assertEquals("0.0.0.0/0", p.toString());
+
+        try {
+            p = new IpPrefix(IPV4_BYTES, 33);
+            fail("Expected IllegalArgumentException: invalid prefix length");
+        } catch(RuntimeException expected) {}
+
+        try {
+            p = new IpPrefix(IPV4_BYTES, 128);
+            fail("Expected IllegalArgumentException: invalid prefix length");
+        } catch(RuntimeException expected) {}
+
+        try {
+            p = new IpPrefix(IPV4_BYTES, -1);
+            fail("Expected IllegalArgumentException: negative prefix length");
+        } catch(RuntimeException expected) {}
+
+        p = new IpPrefix(IPV6_BYTES, 128);
+        assertEquals("2001:db8:dead:beef:f00::a0/128", p.toString());
+
+        p = new IpPrefix(IPV6_BYTES, 122);
+        assertEquals("2001:db8:dead:beef:f00::80/122", p.toString());
+
+        p = new IpPrefix(IPV6_BYTES, 64);
+        assertEquals("2001:db8:dead:beef::/64", p.toString());
+
+        p = new IpPrefix(IPV6_BYTES, 3);
+        assertEquals("2000::/3", p.toString());
+
+        p = new IpPrefix(IPV6_BYTES, 0);
+        assertEquals("::/0", p.toString());
+
+        try {
+            p = new IpPrefix(IPV6_BYTES, -1);
+            fail("Expected IllegalArgumentException: negative prefix length");
+        } catch(RuntimeException expected) {}
+
+        try {
+            p = new IpPrefix(IPV6_BYTES, 129);
+            fail("Expected IllegalArgumentException: negative prefix length");
+        } catch(RuntimeException expected) {}
+
+    }
+
+    private void assertAreEqual(Object o1, Object o2) {
+        assertTrue(o1.equals(o2));
+        assertTrue(o2.equals(o1));
+    }
+
+    private void assertAreNotEqual(Object o1, Object o2) {
+        assertFalse(o1.equals(o2));
+        assertFalse(o2.equals(o1));
+    }
+
+    @SmallTest
+    public void testEquals() {
+        IpPrefix p1, p2;
+
+        p1 = new IpPrefix("192.0.2.251/23");
+        p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23);
+        assertAreEqual(p1, p2);
+
+        p1 = new IpPrefix("192.0.2.5/23");
+        assertAreEqual(p1, p2);
+
+        p1 = new IpPrefix("192.0.2.5/24");
+        assertAreNotEqual(p1, p2);
+
+        p1 = new IpPrefix("192.0.4.5/23");
+        assertAreNotEqual(p1, p2);
+
+
+        p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122");
+        p2 = new IpPrefix(IPV6_BYTES, 122);
+        assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString());
+        assertAreEqual(p1, p2);
+
+        p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122");
+        assertAreEqual(p1, p2);
+
+        p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123");
+        assertAreNotEqual(p1, p2);
+
+        p1 = new IpPrefix("2001:db8:dead:beef::/122");
+        assertAreNotEqual(p1, p2);
+
+        // 192.0.2.4/32 != c000:0204::/32.
+        byte[] ipv6bytes = new byte[16];
+        System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length);
+        p1 = new IpPrefix(ipv6bytes, 32);
+        assertAreEqual(p1, new IpPrefix("c000:0204::/32"));
+
+        p2 = new IpPrefix(IPV4_BYTES, 32);
+        assertAreNotEqual(p1, p2);
+    }
+
+    @SmallTest
+    public void testHashCode() {
+        IpPrefix p;
+        int oldCode = -1;
+        Random random = new Random();
+        for (int i = 0; i < 100; i++) {
+            if (random.nextBoolean()) {
+                // IPv4.
+                byte[] b = new byte[4];
+                random.nextBytes(b);
+                p = new IpPrefix(b, random.nextInt(33));
+                assertNotEqual(oldCode, p.hashCode());
+                oldCode = p.hashCode();
+            } else {
+                // IPv6.
+                byte[] b = new byte[16];
+                random.nextBytes(b);
+                p = new IpPrefix(b, random.nextInt(129));
+                assertNotEqual(oldCode, p.hashCode());
+                oldCode = p.hashCode();
+            }
+        }
+    }
+
+    @SmallTest
+    public void testMappedAddressesAreBroken() {
+        // 192.0.2.0/24 != ::ffff:c000:0204/120, but because we use InetAddress,
+        // we are unable to comprehend that.
+        byte[] ipv6bytes = {
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff,
+            (byte) 192, (byte) 0, (byte) 2, (byte) 0};
+        IpPrefix p = new IpPrefix(ipv6bytes, 120);
+        assertEquals(16, p.getRawAddress().length);       // Fine.
+        assertArrayEquals(ipv6bytes, p.getRawAddress());  // Fine.
+
+        // Broken.
+        assertEquals("192.0.2.0/120", p.toString());
+        assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress());
+    }
+
+    public IpPrefix passThroughParcel(IpPrefix p) {
+        Parcel parcel = Parcel.obtain();
+        IpPrefix p2 = null;
+        try {
+            p.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            p2 = IpPrefix.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+        assertNotNull(p2);
+        return p2;
+    }
+
+    public void assertParcelingIsLossless(IpPrefix p) {
+      IpPrefix p2 = passThroughParcel(p);
+      assertEquals(p, p2);
+    }
+
+    public void testParceling() {
+        IpPrefix p;
+
+        p = new IpPrefix("2001:4860:db8::/64");
+        assertParcelingIsLossless(p);
+
+        p = new IpPrefix("192.0.2.0/25");
+        assertParcelingIsLossless(p);
+    }
+}
diff --git a/core/tests/coretests/src/android/net/LinkAddressTest.java b/core/tests/coretests/src/android/net/LinkAddressTest.java
index 814ecdd..7bc3974 100644
--- a/core/tests/coretests/src/android/net/LinkAddressTest.java
+++ b/core/tests/coretests/src/android/net/LinkAddressTest.java
@@ -30,6 +30,7 @@
 import android.net.LinkAddress;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import static android.test.MoreAsserts.assertNotEqual;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import static android.system.OsConstants.IFA_F_DEPRECATED;
@@ -50,6 +51,17 @@
     private static final InetAddress V4_ADDRESS = NetworkUtils.numericToInetAddress(V4);
     private static final InetAddress V6_ADDRESS = NetworkUtils.numericToInetAddress(V6);
 
+    public void testConstants() {
+        // RT_SCOPE_UNIVERSE = 0, but all the other constants should be nonzero.
+        assertNotEqual(0, RT_SCOPE_HOST);
+        assertNotEqual(0, RT_SCOPE_LINK);
+        assertNotEqual(0, RT_SCOPE_SITE);
+
+        assertNotEqual(0, IFA_F_DEPRECATED);
+        assertNotEqual(0, IFA_F_PERMANENT);
+        assertNotEqual(0, IFA_F_TENTATIVE);
+    }
+
     public void testConstructors() throws SocketException {
         LinkAddress address;
 
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index e649baa..4015b3d 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -38,6 +38,7 @@
 
     private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
     private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+    private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
 
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
         // Check implementation of equals(), element by element.
@@ -245,11 +246,15 @@
         // Add a route with no interface to a LinkProperties with no interface. No errors.
         LinkProperties lp = new LinkProperties();
         RouteInfo r = new RouteInfo(prefix, address, null);
-        lp.addRoute(r);
+        assertTrue(lp.addRoute(r));
         assertEquals(1, lp.getRoutes().size());
         assertAllRoutesHaveInterface(null, lp);
 
-        // Add a route with an interface. Except an exception.
+        // Adding the same route twice has no effect.
+        assertFalse(lp.addRoute(r));
+        assertEquals(1, lp.getRoutes().size());
+
+        // Add a route with an interface. Expect an exception.
         r = new RouteInfo(prefix, address, "wlan0");
         try {
           lp.addRoute(r);
@@ -267,6 +272,7 @@
         } catch (IllegalArgumentException expected) {}
 
         // If the interface name matches, the route is added.
+        r = new RouteInfo(prefix, null, "wlan0");
         lp.setInterfaceName("wlan0");
         lp.addRoute(r);
         assertEquals(2, lp.getRoutes().size());
@@ -352,7 +358,7 @@
 
         // No addresses.
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
 
         // Addresses on stacked links don't count.
         LinkProperties stacked = new LinkProperties();
@@ -361,12 +367,12 @@
         stacked.addLinkAddress(LINKADDRV4);
         stacked.addLinkAddress(LINKADDRV6);
         assertTrue(stacked.hasIPv4Address());
-        assertTrue(stacked.hasIPv6Address());
+        assertTrue(stacked.hasGlobalIPv6Address());
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
         lp.removeStackedLink(stacked);
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
 
         // Addresses on the base link.
         // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
@@ -375,19 +381,29 @@
         assertTrue(lp.addLinkAddress(LINKADDRV6));
         assertEquals(1, lp.getLinkAddresses().size());
         assertFalse(lp.hasIPv4Address());
-        assertTrue(lp.hasIPv6Address());
+        assertTrue(lp.hasGlobalIPv6Address());
 
         assertTrue(lp.removeLinkAddress(LINKADDRV6));
         assertEquals(0, lp.getLinkAddresses().size());
-        assertTrue(lp.addLinkAddress(LINKADDRV4));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
 
-        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV4));
         assertEquals(2, lp.getLinkAddresses().size());
         assertTrue(lp.hasIPv4Address());
-        assertTrue(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertEquals(3, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(2, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
 
         // Adding an address twice has no effect.
         // Removing an address that's not present has no effect.
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index cac6b93..ff2c8f0 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -20,8 +20,10 @@
 import android.content.Context;
 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.net.NetworkScorerAppManager.NetworkScorerAppData;
 import android.test.InstrumentationTestCase;
 
 import com.google.android.collect.Lists;
@@ -63,11 +65,11 @@
 
         setScorers(package1, package2, package3);
 
-        Iterator<String> result =
+        Iterator<NetworkScorerAppData> result =
                 NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator();
 
         assertTrue(result.hasNext());
-        assertEquals("package1", result.next());
+        assertEquals("package1", result.next().mPackageName);
 
         assertFalse(result.hasNext());
     }
@@ -93,6 +95,7 @@
         ResolveInfo resolveInfo = new ResolveInfo();
         resolveInfo.activityInfo = new ActivityInfo();
         resolveInfo.activityInfo.packageName = packageName;
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
         if (hasReceiverPermission) {
             resolveInfo.activityInfo.permission = permission.BROADCAST_SCORE_NETWORKS;
         }
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
index c80d0bf..dcacd11 100644
--- a/core/tests/coretests/src/android/net/RouteInfoTest.java
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -19,7 +19,7 @@
 import java.lang.reflect.Method;
 import java.net.InetAddress;
 
-import android.net.LinkAddress;
+import android.net.IpPrefix;
 import android.net.RouteInfo;
 import android.os.Parcel;
 
@@ -32,9 +32,8 @@
         return InetAddress.parseNumericAddress(addr);
     }
 
-    private LinkAddress Prefix(String prefix) {
-        String[] parts = prefix.split("/");
-        return new LinkAddress(Address(parts[0]), Integer.parseInt(parts[1]));
+    private IpPrefix Prefix(String prefix) {
+        return new IpPrefix(prefix);
     }
 
     @SmallTest
@@ -43,17 +42,17 @@
 
         // Invalid input.
         try {
-            r = new RouteInfo((LinkAddress) null, null, "rmnet0");
+            r = new RouteInfo((IpPrefix) null, null, "rmnet0");
             fail("Expected RuntimeException:  destination and gateway null");
         } catch(RuntimeException e) {}
 
         // Null destination is default route.
-        r = new RouteInfo((LinkAddress) null, Address("2001:db8::1"), null);
+        r = new RouteInfo((IpPrefix) null, Address("2001:db8::1"), null);
         assertEquals(Prefix("::/0"), r.getDestination());
         assertEquals(Address("2001:db8::1"), r.getGateway());
         assertNull(r.getInterface());
 
-        r = new RouteInfo((LinkAddress) null, Address("192.0.2.1"), "wlan0");
+        r = new RouteInfo((IpPrefix) null, Address("192.0.2.1"), "wlan0");
         assertEquals(Prefix("0.0.0.0/0"), r.getDestination());
         assertEquals(Address("192.0.2.1"), r.getGateway());
         assertEquals("wlan0", r.getInterface());
@@ -74,7 +73,7 @@
         class PatchedRouteInfo {
             private final RouteInfo mRouteInfo;
 
-            public PatchedRouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
+            public PatchedRouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
                 mRouteInfo = new RouteInfo(destination, gateway, iface);
             }
 
@@ -151,38 +150,68 @@
         assertAreNotEqual(r1, r3);
     }
 
-    public void testHostRoute() {
+    public void testHostAndDefaultRoutes() {
       RouteInfo r;
 
       r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
       assertFalse(r.isHostRoute());
+      assertTrue(r.isDefaultRoute());
+      assertTrue(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
       assertFalse(r.isHostRoute());
+      assertTrue(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertTrue(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
       assertFalse(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
       assertFalse(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
 
       r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
       assertTrue(r.isHostRoute());
+      assertFalse(r.isDefaultRoute());
+      assertFalse(r.isIPv4Default());
+      assertFalse(r.isIPv6Default());
     }
 
     public RouteInfo passThroughParcel(RouteInfo r) {
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/CursorAnchorInfoTest.java b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
index d850c7c..9252270 100644
--- a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
+++ b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
@@ -20,6 +20,7 @@
 import android.graphics.RectF;
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.CursorAnchorInfo.Builder;
 
@@ -81,7 +82,7 @@
         assertEquals(SELECTION_START, info.getSelectionStart());
         assertEquals(SELECTION_END, info.getSelectionEnd());
         assertEquals(COMPOSING_TEXT_START, info.getComposingTextStart());
-        assertEquals(COMPOSING_TEXT, info.getComposingText());
+        assertTrue(TextUtils.equals(COMPOSING_TEXT, info.getComposingText()));
         assertEquals(INSERTION_MARKER_HORIZONTAL, info.getInsertionMarkerHorizontal());
         assertEquals(INSERTION_MARKER_TOP, info.getInsertionMarkerTop());
         assertEquals(INSERTION_MARKER_BASELINE, info.getInsertionMarkerBaseline());
@@ -97,7 +98,7 @@
         assertEquals(SELECTION_START, info2.getSelectionStart());
         assertEquals(SELECTION_END, info2.getSelectionEnd());
         assertEquals(COMPOSING_TEXT_START, info2.getComposingTextStart());
-        assertEquals(COMPOSING_TEXT, info2.getComposingText());
+        assertTrue(TextUtils.equals(COMPOSING_TEXT, info2.getComposingText()));
         assertEquals(INSERTION_MARKER_HORIZONTAL, info2.getInsertionMarkerHorizontal());
         assertEquals(INSERTION_MARKER_TOP, info2.getInsertionMarkerTop());
         assertEquals(INSERTION_MARKER_BASELINE, info2.getInsertionMarkerBaseline());
@@ -110,12 +111,12 @@
         assertEquals(info, info2);
         assertEquals(info.hashCode(), info2.hashCode());
 
-        // Make sure that object can be marshalled via {@link Parsel}.
+        // Make sure that object can be marshaled via {@link Parsel}.
         final CursorAnchorInfo info3 = cloneViaParcel(info2);
         assertEquals(SELECTION_START, info3.getSelectionStart());
         assertEquals(SELECTION_END, info3.getSelectionEnd());
         assertEquals(COMPOSING_TEXT_START, info3.getComposingTextStart());
-        assertEquals(COMPOSING_TEXT, info3.getComposingText());
+        assertTrue(TextUtils.equals(COMPOSING_TEXT, info3.getComposingText()));
         assertEquals(INSERTION_MARKER_HORIZONTAL, info3.getInsertionMarkerHorizontal());
         assertEquals(INSERTION_MARKER_TOP, info3.getInsertionMarkerTop());
         assertEquals(INSERTION_MARKER_BASELINE, info3.getInsertionMarkerBaseline());
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/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
index 58b7db9..42de9ea 100644
--- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
@@ -36,8 +36,7 @@
         }
     }
 
-    private void setLocale(String code) {
-        Locale locale = new Locale(code);
+    private void setLocale(Locale locale) {
         Locale.setDefault(locale);
         Configuration config = new Configuration();
         config.locale = locale;
@@ -268,193 +267,193 @@
      */
     public void testMatrix100000() throws Throwable {
         final int resId = R.integer.matrix_100000;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 100, 100);
     }
 
     public void testMatrix100001() throws Throwable {
         final int resId = R.integer.matrix_100001;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 100, 600);
     }
 
     public void testMatrix100010() throws Throwable {
         final int resId = R.integer.matrix_100010;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 100, 500);
     }
 
     public void testMatrix100011() throws Throwable {
         final int resId = R.integer.matrix_100011;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 100, 600);
     }
 
     public void testMatrix100100() throws Throwable {
         final int resId = R.integer.matrix_100100;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 400);
     }
 
     public void testMatrix100101() throws Throwable {
         final int resId = R.integer.matrix_100101;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 600);
     }
 
     public void testMatrix100110() throws Throwable {
         final int resId = R.integer.matrix_100110;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 400);
     }
 
     public void testMatrix100111() throws Throwable {
         final int resId = R.integer.matrix_100111;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 600);
     }
 
     public void testMatrix101000() throws Throwable {
         final int resId = R.integer.matrix_101000;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 300, 300);
     }
 
     public void testMatrix101001() throws Throwable {
         final int resId = R.integer.matrix_101001;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 300, 600);
     }
 
     public void testMatrix101010() throws Throwable {
         final int resId = R.integer.matrix_101010;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 300, 500);
     }
 
     public void testMatrix101011() throws Throwable {
         final int resId = R.integer.matrix_101011;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 300, 600);
     }
 
     public void testMatrix101100() throws Throwable {
         final int resId = R.integer.matrix_101100;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 400);
     }
 
     public void testMatrix101101() throws Throwable {
         final int resId = R.integer.matrix_101101;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 600);
     }
 
     public void testMatrix101110() throws Throwable {
         final int resId = R.integer.matrix_101110;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 400);
     }
 
     public void testMatrix101111() throws Throwable {
         final int resId = R.integer.matrix_101111;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 100, 400, 600);
     }
 
     public void testMatrix110000() throws Throwable {
         final int resId = R.integer.matrix_110000;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 200);
     }
 
     public void testMatrix110001() throws Throwable {
         final int resId = R.integer.matrix_110001;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 600);
     }
 
     public void testMatrix110010() throws Throwable {
         final int resId = R.integer.matrix_110010;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 200);
     }
 
     public void testMatrix110011() throws Throwable {
         final int resId = R.integer.matrix_110011;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 600);
     }
 
     public void testMatrix110100() throws Throwable {
         final int resId = R.integer.matrix_110100;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 400);
     }
 
     public void testMatrix110101() throws Throwable {
         final int resId = R.integer.matrix_110101;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 600);
     }
 
     public void testMatrix110110() throws Throwable {
         final int resId = R.integer.matrix_110110;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 400);
     }
 
     public void testMatrix110111() throws Throwable {
         final int resId = R.integer.matrix_110111;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 600);
     }
 
     public void testMatrix111000() throws Throwable {
         final int resId = R.integer.matrix_111000;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 200);
     }
 
     public void testMatrix111001() throws Throwable {
         final int resId = R.integer.matrix_111001;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 600);
     }
 
     public void testMatrix111010() throws Throwable {
         final int resId = R.integer.matrix_111010;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 200);
     }
 
     public void testMatrix111011() throws Throwable {
         final int resId = R.integer.matrix_111011;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 200, 600);
     }
 
     public void testMatrix111100() throws Throwable {
         final int resId = R.integer.matrix_111100;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 400);
     }
 
     public void testMatrix111101() throws Throwable {
         final int resId = R.integer.matrix_111101;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 600);
     }
 
     public void testMatrix111110() throws Throwable {
         final int resId = R.integer.matrix_111110;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 400);
     }
 
     public void testMatrix111111() throws Throwable {
         final int resId = R.integer.matrix_111111;
-        setLocale("sv_SE");
+        setLocale(new Locale("sv", "SE"));
         assertResource(resId, 200, 400, 600);
     }
 }
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index c6bccfe..6437e07 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -42,12 +42,21 @@
 $(eval $(call create-font-symlink,DroidSerif-Italic.ttf,NotoSerif-Italic.ttf))
 $(eval $(call create-font-symlink,DroidSerif-BoldItalic.ttf,NotoSerif-BoldItalic.ttf))
 
+extra_font_files := \
+    DroidSans.ttf \
+    DroidSans-Bold.ttf
+
 ################################
 # On space-constrained devices, we include a subset of fonts:
 ifeq ($(SMALLER_FONT_FOOTPRINT),true)
+
 droidsans_fallback_src := DroidSansFallback.ttf
-extra_font_files := DroidSans.ttf DroidSans-Bold.ttf
-else
+
+else  # !SMALLER_FONT_FOOTPRINT
+
+droidsans_fallback_src := DroidSansFallbackFull.ttf
+
+ifneq ($(EXTENDED_FONT_FOOTPRINT),true)
 include $(CLEAR_VARS)
 LOCAL_MODULE := MTLmr3m.ttf
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
@@ -55,12 +64,9 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_OUT)/fonts
 include $(BUILD_PREBUILT)
+extra_font_files += MTLmr3m.ttf
+endif  # !EXTENDED_FONT_FOOTPRINT
 
-droidsans_fallback_src := DroidSansFallbackFull.ttf
-extra_font_files := \
-	DroidSans.ttf \
-	DroidSans-Bold.ttf \
-	MTLmr3m.ttf
 endif  # SMALLER_FONT_FOOTPRINT
 
 ################################
@@ -106,6 +112,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 +127,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/DroidSansFallback.ttf b/data/fonts/DroidSansFallback.ttf
index 2b75113..8e70e80 100644
--- a/data/fonts/DroidSansFallback.ttf
+++ b/data/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallbackFull.ttf b/data/fonts/DroidSansFallbackFull.ttf
index a9df005..68d2751 100644
--- a/data/fonts/DroidSansFallbackFull.ttf
+++ b/data/fonts/DroidSansFallbackFull.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallbackLegacy.ttf b/data/fonts/DroidSansFallbackLegacy.ttf
deleted file mode 100644
index 61460b1..0000000
--- a/data/fonts/DroidSansFallbackLegacy.ttf
+++ /dev/null
Binary files differ
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/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index c2d5afe..37e9b15 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -219,6 +219,41 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansCherokee-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansCanadianAboriginal-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansYi-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file lang="zh-CN">NotoSansHans-Regular.otf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file lang="zh-TW">NotoSansHant-Regular.otf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file lang="ja">NotoSansJP-Regular.otf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file lang="ko">NotoSansKR-Regular.otf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NanumGothic.ttf</file>
         </fileset>
     </family>
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/auto/images/assets/00_frame.png b/docs/html/auto/images/assets/00_frame.png
new file mode 100644
index 0000000..2a78380
--- /dev/null
+++ b/docs/html/auto/images/assets/00_frame.png
Binary files differ
diff --git a/docs/html/auto/images/assets/01_a_maps.png b/docs/html/auto/images/assets/01_a_maps.png
new file mode 100644
index 0000000..0d1b219
--- /dev/null
+++ b/docs/html/auto/images/assets/01_a_maps.png
Binary files differ
diff --git a/docs/html/auto/images/assets/01_b_now.png b/docs/html/auto/images/assets/01_b_now.png
new file mode 100644
index 0000000..043ec74
--- /dev/null
+++ b/docs/html/auto/images/assets/01_b_now.png
Binary files differ
diff --git a/docs/html/auto/images/assets/02_a_notif.gif b/docs/html/auto/images/assets/02_a_notif.gif
new file mode 100644
index 0000000..9f3d35d
--- /dev/null
+++ b/docs/html/auto/images/assets/02_a_notif.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/02_b_switcher.gif b/docs/html/auto/images/assets/02_b_switcher.gif
new file mode 100644
index 0000000..c4014e3
--- /dev/null
+++ b/docs/html/auto/images/assets/02_b_switcher.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/03_a_musict.png b/docs/html/auto/images/assets/03_a_musict.png
new file mode 100644
index 0000000..4e11243
--- /dev/null
+++ b/docs/html/auto/images/assets/03_a_musict.png
Binary files differ
diff --git a/docs/html/auto/images/assets/03_b_voice.gif b/docs/html/auto/images/assets/03_b_voice.gif
new file mode 100644
index 0000000..b280a9b
--- /dev/null
+++ b/docs/html/auto/images/assets/03_b_voice.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/do_01_switcher.png b/docs/html/auto/images/assets/do_01_switcher.png
new file mode 100644
index 0000000..b654a13
--- /dev/null
+++ b/docs/html/auto/images/assets/do_01_switcher.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_02_music.png b/docs/html/auto/images/assets/do_02_music.png
new file mode 100644
index 0000000..59fc8df
--- /dev/null
+++ b/docs/html/auto/images/assets/do_02_music.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_02_music_night.png b/docs/html/auto/images/assets/do_02_music_night.png
new file mode 100644
index 0000000..7faccfb
--- /dev/null
+++ b/docs/html/auto/images/assets/do_02_music_night.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_03_more.png b/docs/html/auto/images/assets/do_03_more.png
new file mode 100644
index 0000000..17040d1
--- /dev/null
+++ b/docs/html/auto/images/assets/do_03_more.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_04_mdrawer.png b/docs/html/auto/images/assets/do_04_mdrawer.png
new file mode 100644
index 0000000..de94c22
--- /dev/null
+++ b/docs/html/auto/images/assets/do_04_mdrawer.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_05_template.png b/docs/html/auto/images/assets/do_05_template.png
new file mode 100644
index 0000000..63a07d8
--- /dev/null
+++ b/docs/html/auto/images/assets/do_05_template.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_05_template_night.png b/docs/html/auto/images/assets/do_05_template_night.png
new file mode 100644
index 0000000..6acd259
--- /dev/null
+++ b/docs/html/auto/images/assets/do_05_template_night.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_06_gdrawer.png b/docs/html/auto/images/assets/do_06_gdrawer.png
new file mode 100644
index 0000000..b02c0e6
--- /dev/null
+++ b/docs/html/auto/images/assets/do_06_gdrawer.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_07_notif.png b/docs/html/auto/images/assets/do_07_notif.png
new file mode 100644
index 0000000..50ea565
--- /dev/null
+++ b/docs/html/auto/images/assets/do_07_notif.png
Binary files differ
diff --git a/docs/html/auto/images/figure01.png b/docs/html/auto/images/figure01.png
new file mode 100644
index 0000000..3044020
--- /dev/null
+++ b/docs/html/auto/images/figure01.png
Binary files differ
diff --git a/docs/html/auto/images/hero.jpg b/docs/html/auto/images/hero.jpg
new file mode 100644
index 0000000..34b5b5d
--- /dev/null
+++ b/docs/html/auto/images/hero.jpg
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/iheartradio.png b/docs/html/auto/images/logos/apps/iheartradio.png
new file mode 100644
index 0000000..8feb38d
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/iheartradio.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/joyride.png b/docs/html/auto/images/logos/apps/joyride.png
new file mode 100644
index 0000000..8c2897e
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/joyride.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/mlb.png b/docs/html/auto/images/logos/apps/mlb.png
new file mode 100644
index 0000000..ddde4cd
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/mlb.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/pandora.png b/docs/html/auto/images/logos/apps/pandora.png
new file mode 100644
index 0000000..13fa1d3
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/pandora.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/pocketcasts.png b/docs/html/auto/images/logos/apps/pocketcasts.png
new file mode 100644
index 0000000..5227937
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/pocketcasts.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/songza.png b/docs/html/auto/images/logos/apps/songza.png
new file mode 100644
index 0000000..3bd7107
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/songza.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/spotify.png b/docs/html/auto/images/logos/apps/spotify.png
new file mode 100644
index 0000000..d039fd3
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/spotify.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/stitcher.png b/docs/html/auto/images/logos/apps/stitcher.png
new file mode 100644
index 0000000..b77da12
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/stitcher.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/tunein.png b/docs/html/auto/images/logos/apps/tunein.png
new file mode 100644
index 0000000..137ba81
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/tunein.png
Binary files differ
diff --git a/docs/html/auto/images/logos/apps/umano.png b/docs/html/auto/images/logos/apps/umano.png
new file mode 100644
index 0000000..7b59f95
--- /dev/null
+++ b/docs/html/auto/images/logos/apps/umano.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/abarth.png b/docs/html/auto/images/logos/auto/abarth.png
new file mode 100644
index 0000000..fd1d228
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/abarth.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/acura.png b/docs/html/auto/images/logos/auto/acura.png
new file mode 100644
index 0000000..4ba69cc
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/acura.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/alpharomeo.png b/docs/html/auto/images/logos/auto/alpharomeo.png
new file mode 100644
index 0000000..51c8c58
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/alpharomeo.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/audi.png b/docs/html/auto/images/logos/auto/audi.png
new file mode 100644
index 0000000..86cb06b
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/audi.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/bentley.png b/docs/html/auto/images/logos/auto/bentley.png
new file mode 100644
index 0000000..ed8cf47
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/bentley.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/chevrolet.png b/docs/html/auto/images/logos/auto/chevrolet.png
new file mode 100644
index 0000000..e0980b0
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/chevrolet.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/chrysler.png b/docs/html/auto/images/logos/auto/chrysler.png
new file mode 100644
index 0000000..c670c19
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/chrysler.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/datsun.png b/docs/html/auto/images/logos/auto/datsun.png
new file mode 100644
index 0000000..059ffd8
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/datsun.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/dodge.png b/docs/html/auto/images/logos/auto/dodge.png
new file mode 100644
index 0000000..4d6fcc0
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/dodge.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/fiat.png b/docs/html/auto/images/logos/auto/fiat.png
new file mode 100644
index 0000000..b9783d9
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/fiat.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/ford.png b/docs/html/auto/images/logos/auto/ford.png
new file mode 100644
index 0000000..86a2670
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/ford.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/honda.png b/docs/html/auto/images/logos/auto/honda.png
new file mode 100644
index 0000000..62ee38f
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/honda.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/hyundai.png b/docs/html/auto/images/logos/auto/hyundai.png
new file mode 100644
index 0000000..4744641
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/hyundai.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/infinity.png b/docs/html/auto/images/logos/auto/infinity.png
new file mode 100644
index 0000000..a20e0a0
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/infinity.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/jeep.png b/docs/html/auto/images/logos/auto/jeep.png
new file mode 100644
index 0000000..024c3d1
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/jeep.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/kia.png b/docs/html/auto/images/logos/auto/kia.png
new file mode 100644
index 0000000..f8046d9
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/kia.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/maserati.png b/docs/html/auto/images/logos/auto/maserati.png
new file mode 100644
index 0000000..22bcf6a
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/maserati.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/mazda.png b/docs/html/auto/images/logos/auto/mazda.png
new file mode 100644
index 0000000..2281c90
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/mazda.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/mitsubishi.png b/docs/html/auto/images/logos/auto/mitsubishi.png
new file mode 100644
index 0000000..1405b19
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/mitsubishi.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/nissan.png b/docs/html/auto/images/logos/auto/nissan.png
new file mode 100644
index 0000000..63bc4f7
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/nissan.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/opel.png b/docs/html/auto/images/logos/auto/opel.png
new file mode 100644
index 0000000..7e25ed5
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/opel.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/ram.png b/docs/html/auto/images/logos/auto/ram.png
new file mode 100644
index 0000000..9702587
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/ram.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/renault.png b/docs/html/auto/images/logos/auto/renault.png
new file mode 100644
index 0000000..d252aa3
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/renault.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/seat.png b/docs/html/auto/images/logos/auto/seat.png
new file mode 100644
index 0000000..9802ccf
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/seat.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/skoda.png b/docs/html/auto/images/logos/auto/skoda.png
new file mode 100755
index 0000000..7bc06b0
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/skoda.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/subaru.png b/docs/html/auto/images/logos/auto/subaru.png
new file mode 100644
index 0000000..7407e61
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/subaru.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/suzuki.png b/docs/html/auto/images/logos/auto/suzuki.png
new file mode 100644
index 0000000..85fd7e5
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/suzuki.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/volkswagen.png b/docs/html/auto/images/logos/auto/volkswagen.png
new file mode 100644
index 0000000..febadf4
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/volkswagen.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/volvo.png b/docs/html/auto/images/logos/auto/volvo.png
new file mode 100644
index 0000000..683af26
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/volvo.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/alpine.png b/docs/html/auto/images/logos/hard/alpine.png
new file mode 100644
index 0000000..55f0e8a
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/alpine.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/clarion.png b/docs/html/auto/images/logos/hard/clarion.png
new file mode 100644
index 0000000..2288b99
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/clarion.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/cloudcar.png b/docs/html/auto/images/logos/hard/cloudcar.png
new file mode 100644
index 0000000..9e7e8b7
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/cloudcar.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/delphi.png b/docs/html/auto/images/logos/hard/delphi.png
new file mode 100644
index 0000000..a2a03de
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/delphi.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/freescale.png b/docs/html/auto/images/logos/hard/freescale.png
new file mode 100644
index 0000000..2909944
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/freescale.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/fujitsuten.png b/docs/html/auto/images/logos/hard/fujitsuten.png
new file mode 100644
index 0000000..70269e3
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/fujitsuten.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/harman.png b/docs/html/auto/images/logos/hard/harman.png
new file mode 100644
index 0000000..0fb295a
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/harman.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/jvc.png b/docs/html/auto/images/logos/hard/jvc.png
new file mode 100644
index 0000000..6a9c7ad
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/jvc.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/kenwood.png b/docs/html/auto/images/logos/hard/kenwood.png
new file mode 100644
index 0000000..3c80098
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/kenwood.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/lg.png b/docs/html/auto/images/logos/hard/lg.png
new file mode 100644
index 0000000..52feeff
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/lg.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/panasonic.png b/docs/html/auto/images/logos/hard/panasonic.png
new file mode 100644
index 0000000..b6aaaf1
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/panasonic.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/parrot.png b/docs/html/auto/images/logos/hard/parrot.png
new file mode 100644
index 0000000..d5bf41f
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/parrot.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/pioneer.png b/docs/html/auto/images/logos/hard/pioneer.png
new file mode 100644
index 0000000..307d71a
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/pioneer.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/renesas.png b/docs/html/auto/images/logos/hard/renesas.png
new file mode 100644
index 0000000..69ecab7
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/renesas.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/symphonyteleca.png b/docs/html/auto/images/logos/hard/symphonyteleca.png
new file mode 100644
index 0000000..0b3ebe5
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/symphonyteleca.png
Binary files differ
diff --git a/docs/html/auto/images/logos/hard/texasinstruments.png b/docs/html/auto/images/logos/hard/texasinstruments.png
new file mode 100644
index 0000000..87d80cb
--- /dev/null
+++ b/docs/html/auto/images/logos/hard/texasinstruments.png
Binary files differ
diff --git a/docs/html/auto/index.jd b/docs/html/auto/index.jd
new file mode 100644
index 0000000..63ac287
--- /dev/null
+++ b/docs/html/auto/index.jd
@@ -0,0 +1,487 @@
+page.title=Android Auto
+page.viewport_width=970
+fullpage=true
+no_footer_links=true
+page.type=about
+
+@jd:body
+
+<style>
+.fullpage>#footer,
+#jd-content>.content-footer.wrap {
+  display:none;
+}
+#hero-height {
+  height:calc(100% - 100px);
+}
+.img-logo {
+  margin:0 auto;
+  display:block;
+  margin-bottom:-28px !important;
+}
+.img-logo-thin {
+  margin:0 auto;
+  display:block;
+  margin-bottom:-55px !important;
+}
+.col-5 {
+  width:170px;
+}
+.auto-img-container-cols {
+  position:relative;
+  margin-bottom:25px;
+  margin-top:25px;
+}
+.auto-img-frame-cols {
+  z-index:2;
+  position:relative;
+  width:460px;
+}
+.auto-img-shot-cols {
+  width:447px;
+  position:absolute;
+  top:8px;
+  left:7px;
+  z-index:1;
+}
+.sdk-link {
+  font-size:16px;
+  padding-left:5px;
+  padding-right:15px;
+}
+.sdk-link-title {
+  font-size:18px;
+  padding-left:5px;
+  padding-right:15px;
+}
+.sdk-link-desc {
+  font-size:14px;
+  padding-left:5px;
+  padding-right:15px;
+}
+.cols-leftp {
+  padding-left:95px;
+}
+</style>
+
+
+<div class="landing-body-content">
+  <div class="landing-hero-container">
+
+    <div id="hero-height" class="landing-section auto-hero">
+      <div class="landing-hero-scrim"></div>
+      <div class="landing-hero-wrap">
+        <div class="vertical-center-outer">
+          <div class="vertical-center-inner">
+
+            <div class="col-10">
+              <div class="landing-section-header">
+                <div class="landing-h1 hero">Android Auto</div>
+                <div class="landing-subhead hero">Entertainment and services on your dashboard</div>
+                <div class="landing-hero-description">
+                  <p style="width:450px">Display and control your Android app in vehicles.
+                   Integrate your content with easy-to-use APIs and let Android Auto take
+                   care of the rest.</p>
+                </div>
+
+              <div class="landing-body">
+                <a href="{@docRoot}auto/overview.html" class="landing-button landing-primary"
+                   style="margin-top:40px;">
+                  Developer Overview
+                </a>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div> <!-- end .wrap -->
+
+      <div class="landing-scroll-down-affordance">
+        <a class="landing-down-arrow" href="#android-in-car">
+          <img src="{@docRoot}wear/images/carrot.png" alt="Scroll down to read more">
+        </a>
+      </div>
+    </div> <!-- end .landing-section .landing-hero -->
+  </div> <!-- end .landing-hero-container -->
+
+    <div class="landing-rest-of-page">
+
+      <div class="landing-section landing-gray-background" id="android-in-car">
+        <div class="wrap">
+          <div class="landing-section-h1">
+            <div class="landing-h1">Extending Android to Cars</div>
+          </div>
+          <div class="landing-body">
+            <div class="landing-subhead">Android Auto brings the Android experience to
+            cars with apps like Google Now and Maps.</div>
+            <div class="cols">
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/01_b_now.png" />
+                </div>
+              </div>
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/01_a_maps.png" />
+                </div>
+              </div>
+            </div>
+
+            <p>When users connect their Android phones to compatible vehicles, Android Auto
+             shows a standard interface that lets them start enabled apps and services.
+             Android Auto locks the handheld device when connected, so drivers
+            interact with Auto by using the vehicle's input controls, touch display, and voice.</p>
+            </p>
+          </div>
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+
+      <div class="landing-section">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">Build for One Platform</div>
+            <div class="landing-subhead">Create apps with the Android APIs you're familiar with
+            and extend them to cars with the Auto SDK.
+            </div>
+          </div>
+
+          <div class="landing-body">
+
+            <div class="cols">
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/02_b_switcher.gif" />
+                </div>
+              </div>
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/02_a_notif.gif" />
+                </div>
+              </div>
+            </div>
+            <p>Write your apps without having
+            to worry about vehicle-specific hardware differences like screen resolution, software
+            interfaces, knobs, and touch controls. Your users get the same experience on any compatible
+            vehicle such as a consistent app launcher and system events such as notifications.</p>
+          </div>
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+
+      <div class="landing-section landing-gray-background">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">Minimize Distraction</div>
+            <div class="landing-subhead">
+             Android Auto displays different UIs for several app categories that let users focus on the road.
+            </div>
+          </div>
+          <div class="landing-body">
+            <div class="cols">
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/03_b_voice.gif" />
+                </div>
+              </div>
+              <div class="col-8">
+                <div class="auto-img-container-cols">
+                  <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+                  <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/03_a_musict.png" />
+                </div>
+              </div>
+            </div>
+            <p>Android Auto defines the user interaction model for all
+             apps and let you hook into a standard UI with touch and voice controls. The interface
+             is designed to reduce driver distraction while still letting you customize and brand them to properly deliver your content.
+             </p>
+          </div>
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+      <div class="landing-section" style="background-color:#f5f5f5">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-pre-h1">Coming soon</div>
+            <div class="landing-h1">Android Auto SDK</div>
+          </div>
+
+          <div class="landing-body">
+            <p>In the coming months, we’ll be releasing the Android Auto SDK, which includes APIs
+              and tools to make your existing apps compatible with Android Auto. The first version
+              of the SDK will provide APIs for music, podcast, live radio, and audio
+              news apps, as well as limited voice actions.</p>
+
+            <div class="cols" style="margin-top:40px">
+              <div class="col-3-wide">
+                <p class="sdk-link-title">Updates</p>
+                <p class="sdk-link-desc">
+                  Register to receive more information and be notified when the SDK is available.
+                </p>
+                <a class="sdk-link" href="https://docs.google.com/a/google.com/forms/d/1ANgYOoYLkfyZ2JRPSU34Nep5yNaU-Ha2syXJ9b4xLrA/viewform">Sign up for updates</a>
+              </div>
+              <div class="col-3-wide">
+                <p class="sdk-link-title">Google+ Community</p>
+                <p class="sdk-link-desc">
+                  Stay involved, get updates, and exchange experiences with other developers.
+                </p>
+                <a class="sdk-link" href="http://g.co/androidautodev">Discuss on Google+</a>
+              </div>
+              <div class="col-3-wide">
+                <p class="sdk-link-title">Developer Overview</p>
+                <p class="sdk-link-desc">
+                  Learn more about developing apps for Android Auto when the SDK is available.
+                </p>
+                <a class="sdk-link" href="{@docRoot}auto/overview.html">Learn about the platform</a>
+              </div>
+            </div>
+          </div>
+
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+
+
+      <div class="landing-section landing-gray-background" >
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">See What Others Have Done</div>
+            <div class="landing-subhead">We're working with developers to bring many popular apps
+            to Android Auto</div>
+          </div>
+          <div class="landing-body">
+          <div class="cols">
+
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/joyride.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/mlb.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/pandora.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/pocketcasts.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols">
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/songza.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/stitcher.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/tunein.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+            <div class="col-4">
+              <img src="{@docRoot}auto/images/logos/apps/umano.png"
+                   width="160" height="160" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols" style="margin-top:40px">
+              <div class="col-4">
+                <img src="{@docRoot}auto/images/logos/apps/iheartradio.png"
+                     width="160" height="160" class="img-logo" />
+              </div>
+              <div class="col-12">
+                <p><em>
+                "The Android Auto APIs provide an easy way to integrate the most important features and functionality of iHeartRadio’s robust music service into a safety-minded automotive infotainment solution. The process was seamless, utilizing a flexible construct that allowed us to quickly adapt our existing product without losing any of the core experience our listeners know and love."</em></p>
+              </div>
+          </div>
+          <div class="cols" style="margin-top:60px">
+
+              <div class="col-4">
+                <img src="{@docRoot}auto/images/logos/apps/spotify.png"
+                     width="160" height="160" class="img-logo" />
+              </div>
+              <div class="col-12"><p style="margin-top:20px"><em>
+              "Android Auto offers Spotify the exciting opportunity to easily enable safe access to millions of songs while driving. We were able to quickly develop for the platform using the new Android voice and media API extensions. As a result, Android users will soon be able to continue the Spotify experience in their cars, including being able to play any song, artist, album or playlist by voice.</em></p>
+              </div>
+              </div>
+          </div>
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+
+
+
+      <div class="landing-section landing-white-background">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">Building an Ecosystem</div>
+          </div>
+          <div class="landing-body landing-align-center">
+              <div class="landing-subhead">
+                Android Auto is coming soon to new cars from these manufacturers
+              </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/abarth.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/alpharomeo.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/acura.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/audi.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/bentley.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/chevrolet.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/chrysler.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/dodge.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/fiat.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/ford.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/honda.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/hyundai.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/infinity.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/jeep.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/kia.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/maserati.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/mazda.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/mitsubishi.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/nissan.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/opel.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/ram.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/renault.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/seat.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/skoda.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+          <div class="cols cols-leftp">
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/subaru.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/suzuki.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/volkswagen.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+            <div class="col-5">
+              <img src="{@docRoot}auto/images/logos/auto/volvo.png"
+                   width="120" height="120" class="img-logo" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div> <!-- end .landing-rest-of-page -->
+    <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement"
+    style="border-top: none;">
+      <div class="layout-content-col col-16" style="padding-top:4px">
+        <style>#___plusone_0 {float:right !important;}</style>
+        <div class="g-plusone" data-size="medium"></div>
+      </div>
+    </div>
+    <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1">
+      <div id="copyright">
+        Except as noted, this content is
+        licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+        Creative Commons Attribution 2.5</a>. For details and
+        restrictions, see the <a href="{@docRoot}license.html">Content
+        License</a>.
+      </div>
+    </div>
+  </div> <!-- end .landing-hero-container -->
+
+  <script>
+  $("a.landing-down-arrow").on("click", function(e) {
+    $("body").animate({
+      scrollTop: $(".auto-hero").height() + 120
+    }, 1000, "easeOutQuint");
+    e.preventDefault();
+  });
+  </script>
+
+</div> <!-- end landing-body-content -->
diff --git a/docs/html/auto/overview.jd b/docs/html/auto/overview.jd
new file mode 100644
index 0000000..cf63b98
--- /dev/null
+++ b/docs/html/auto/overview.jd
@@ -0,0 +1,411 @@
+fullpage=true
+page.viewport_width=970
+no_footer_links=true
+excludeFromSuggestions=true
+page.metaDescription=Android Auto
+
+@jd:body
+
+<style>
+.jd-descr {
+    height:auto;
+}
+#copyright {
+    margin-top:-35px;
+}
+.auto-img-container {
+  position:relative;
+}
+.auto-img-frame {
+  z-index:2;
+  position:relative;
+}
+.auto-img-shot {
+  position:absolute;
+  top:9px;
+  left:8px;
+  z-index:1;
+}
+.auto-img-container-cols {
+  position:relative;
+  margin-top:10px;
+}
+.auto-img-frame-cols {
+  width:380px;
+  z-index:2;
+  position:relative;
+}
+.auto-img-shot-cols {
+  width:369px;
+  position:absolute;
+  top:7px;
+  left:6px;
+  z-index:1;
+}
+.auto-col-2 {
+  width:380px;
+  display: inline;
+  float: left;
+  margin-left: 10px;
+  margin-right: 10px;
+}
+.auto-img-container-single {
+  width:380px;
+  margin:0 auto;
+  margin-top:20px;
+}
+</style>
+
+<div style="width:780px; margin:0 auto;">
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#design">Design</a>
+    <ol>
+      <li><a href="#designprinciples">Design Principles</a></li>
+      <li><a href="#uioverview">UI Overview</a></li>
+    </ol>
+  </li>
+  <li><a href="#architecture">Architecture</a></li>
+  <li><a href="#ui">User Interface</a>
+    <ol>
+      <li><a href="#launchapp">Launch App</a></li>
+      <li><a href="#useractions">User Actions</a></li>
+      <li><a href="#drawertransitions">Drawer Transitions</a></li>
+      <li><a href="#daynighttransitions">Day and Night Transitions</a></li>
+      <li><a href="#customizeui">Customizing the UI</a></li>
+    </ol>
+  </li>
+  <li><a href="#devprocess">Development Process</a></li>
+  <li><a href="#emulator">Testing Your App</a></li>
+  <li><a href="#running">Running Your App</a></li>
+</ol>
+</div>
+</div>
+
+<h1>Android Auto Developer Overview</h1>
+
+<p>Android Auto extends the Android platform into the car. When users connect
+their Android handheld device to a compatible vehicle, Android Auto provides a car-optimized
+Android experience on the vehicle's screen. Users interact with compatible apps and services
+through voice actions and the vehicle's input controls.</p>
+
+<p>The Android Auto SDK lets you easily extend your existing apps to work in the car, without
+having to worry about vehicle-specific hardware differences. You can use many Android APIs and
+services you are already familiar with. Android Auto provides an easy to use UI model and
+supports notifications and voice actions:</p>
+
+<dl>
+<dt style="margin-bottom:10px"><strong>Media UI</strong></dt>
+<dd style="margin-bottom:20px">
+Android Auto defines interaction models and car-specific UI patterns for apps. The
+first version of Android Auto supports media apps, such as music, podcast, live radio, and
+audio news apps.
+</dd>
+<dt style="margin-bottom:10px"><strong>Notifications</strong></dt>
+<dd style="margin-bottom:20px">
+The platform will integrate with existing Android APIs for notifications. Users will get
+car appropiate notifications from Android apps on the vehicle's screen.</dd>
+
+<dt style="margin-bottom:10px"><strong>Voice Actions</strong></dt>
+<dd style="margin-bottom:20px">
+Android Auto supports a set of voice actions to interact with compatible apps and services.
+Apps can respond to the voice actions they're interested in, such as playing a particular song
+or taking a note.</dd>
+
+<dt style="margin-bottom:10px"><strong>Easy Development Workflow</strong></dt>
+<dd style="margin-bottom:20px">
+To extend an existing Android app for Android Auto, you implement a set of interfaces and
+services defined in the platform. You can reuse existing functionality and many Android APIs
+you already know.</dd>
+</dl>
+
+<p>We’ll release the Android Auto SDK in the coming months, which will let you test your
+Android Auto experience on a regular Android device.</p>
+
+
+<h2 id="design">Design</h2>
+
+<p>Android Auto extends users' digital ecosystem into their cars, allowing drivers to stay
+connected to their virtual worlds while staying focused on the road ahead.</p>
+
+<p>Because driving is the primary activity in the car, any digital experiences should be designed
+to complement and augment that activity. They should never demand the user's attention.</p>
+
+<p>Designing for cars is fundamentally different than designing for phones or tablets, and
+requires rethinking how experiences unfold. Because attention is limited and not all tasks are
+possible in the car, effective apps leverage the entire set of devices that drivers have,
+leveraging the app experience on those devices, outside of the car, to set the stage for simple
+experiences while driving.</p>
+
+<p>Android Auto experiences are:</p>
+
+<p><strong>Glanceable and simple</strong>. Driving requires users' full attention. In-car software
+should not. Android Auto was designed to simplify not only the UI, but to optimize interactions
+and require less thinking, induce lower cognitive load, and ultimately, be safer. Effective apps
+provide just enough information in the minimum amount of time the user needs to glance at it and
+return their attention back to the road. Apps should also reduce the number of features to only
+those that are safe and drive-appropriate.</p>
+
+<p><strong>Predictive, yet predictable</strong>. Android Auto leverages rich, contextual awareness
+to keep the driver informed about important situations during the drive. Rich, timely help is
+combined with predictable functions. Effective apps make use of the patterns for common tasks and
+show timely information only when relevant.</p>
+
+<p><strong>Connected</strong>. By leveraging the user's personal ecosystem of apps and services,
+Android Auto promotes a continuous experience from phone to car to other devices. The user's
+music, destinations, and virtual ecosystem are always available to augment the drive. Experiences
+that leverage personal context and other devices are naturally part of Android Auto.</p>
+
+<p><strong>Naturally integrated</strong>. Android Auto blends the user's apps with the car,
+creating a truly integrated experience that leverages what is unique about each car. By using
+the screens, controls, and capabilities of the vehicle, Android Auto feels like an extension of
+the car.</p>
+
+
+
+
+<h2 id="architecture">Architecture</h2>
+
+<p>The Android Auto app shows your app's customized UI on the vehicle's screen. To communicate
+with the Android Auto app, your media app implements a set of media interfaces.</p>
+
+<div style="width:750px;margin:0 auto">
+<img src="{@docRoot}auto/images/figure01.png" alt="" />
+<p class="img-caption">
+  <strong>Figure 1</strong> - Architecture of Android Auto.
+</p>
+</div>
+
+<p>The architecture consists of the following components:</p>
+
+<p><strong>Media App</strong> - Runs a media service that exposes content through browsing and
+playback APIs. The service provides content to the Android Auto app. This is your Android app.</p>
+
+<p><strong>Android Auto App</strong> - Creates the UI and handles user interactions.
+This app uses a media client to request content from the media service running in the media
+app. The client requests data from the media service and monitors service states.</p>
+
+<p><strong>Vehicle Display</strong> - Shows app content and supports user interaction via
+on-screen soft buttons and other components, such as physical buttons or steering
+wheel controls.</p>
+
+<p>Android media apps must implement binders to these APIs:</p>
+
+<ul>
+<li><strong>Browsing</strong> - Enables a media client to browse a hierarchy of a user’s
+media collection, presented as a virtual file system with containers (similar to directories)
+and items (similar to files).</li>
+<li><strong>Playback</strong> - Enables a media client to control media playback and monitor
+playback state through callbacks.</li>
+</ul>
+
+
+<h2 id="ui">User Interface</h2>
+
+<p>The Android Auto app uses a car-specific UI model to display content and user interaction
+opportunities. Android Auto provides you with a standard UI designed to minimize driver
+distraction. You do not have to test a custom UI for for driver distraction, which is a
+lengthy and expensive process involving multiple legislations across the globe and different
+standards for each vehicle OEM.</p>
+
+<p>The UI defines interfaces for browsing, searching, and listening to content from
+media apps. You can customize the UI colors, action icons, background images, and more.</p>
+
+<h3 id="launchapp">Launcher</h3>
+
+<p>The launcher shows all the compatible media apps installed on the user’s
+Android device and lets users select one of them from an scrollable list:</p>
+
+<div class="auto-img-container-single">
+  <div class="auto-img-container">
+    <img class="auto-img-frame-cols" src="/auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="/auto/images/assets/do_01_switcher.png" />
+  </div>
+  <p class="img-caption" style="margin-top:0px">
+    <strong>Figure 2.</strong> The launcher.
+  </p>
+</div>
+
+<h3>Primary App UI</h3>
+
+<p>After the user selects a media app, the display shows the primary app UI.
+You can customize this UI to show your own icons, app name, and
+background images. Figure 3 shows an example of a customized UI:</p>
+
+<div class="cols">
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_05_template.png" />
+  </div>
+</div>
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_02_music.png" />
+  </div>
+</div>
+</div>
+<p class="img-caption">
+  <strong>Figure 3.</strong> A customized UI.
+</p>
+
+
+
+<h3 id="useractions">User Actions</h3>
+
+<p>The primary app UI supports four main actions on the action bar, four auxiliary actions
+on the overflow bar, and the <em>Return</em> action. You can use standard controls and customize
+the actions and icons, as shown in Figure 4.</p>
+
+<div class="auto-img-container-single">
+  <div class="auto-img-container">
+    <img class="auto-img-frame-cols" src="/auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="/auto/images/assets/do_03_more.png" />
+  </div>
+  <p class="img-caption" style="margin-top:0px">
+    <strong>Figure 4.</strong> Custom extra actions.
+  </p>
+</div>
+
+<h3 id="drawertransitions">Drawer Transitions</h3>
+
+<p>For browse actions, the display shows the drawer transition as shown in Figure 5.</p>
+
+<div class="cols">
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_06_gdrawer.png" />
+  </div>
+</div>
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_04_mdrawer.png" />
+  </div>
+</div>
+</div>
+<p class="img-caption">
+  <strong>Figure 5.</strong> Generic and customized drawers.
+</p>
+
+<p>After the transition from the primary app UI to the drawer UI, the drawer
+appears on the center. The customized drawer UI shows the media containers and
+media files provided by the media service in your app. You can also customize drawers
+with icons for list items.</p>
+
+
+<h3 id="daynighttransitions">Day and Night Transitions</h3>
+
+<p>All the UIs support different color schemes for day and night.
+The platform provides the state (day or night) and makes adjustments automatically.</p>
+
+<div class="cols">
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_02_music.png" />
+  </div>
+</div>
+<div class="auto-col-2">
+  <div class="auto-img-container-cols">
+    <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+    <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_02_music_night.png" />
+  </div>
+</div>
+</div>
+<p class="img-caption">
+  <strong>Figure 6.</strong> Day and night modes.
+</p>
+
+<h3 id="customizeui">Customizing UIs</h3>
+
+<p>To customize the UI, you provide the following app-specific resources and actions
+to the Android Auto media client:</p>
+
+<ul>
+<li><strong>Resources</strong> - App logo, app name, theme colors, and background images.</li>
+<li><strong>Actions</strong> - Multiple custom actions; for example: <em>Thumbs Up/Down</em>,
+<em>Favorite</em>, and <em>Bookmark</em>. These actions are app-specific.</li>
+</ul>
+
+<p>If provided, the media client automatically uses them in the UI.</p>
+
+
+<h2 id="devprocess">Development Process</h2>
+
+<p class="note"><strong>Note:</strong> When released, the Android Auto SDK will provide
+media service interfaces, an APK for handheld devices that simulates the Android Auto
+app, and other tools for Android Auto development.</p>
+
+<p>To create a media app for Android Auto, you include an Android service in your app
+that implements the media service interfaces provided by the Android Auto SDK. These
+interfaces define functionality for browsing and finding content, playing media,
+customizing the UI, and performing app-specific actions.</p>
+
+<p>The media service interfaces present the content library as a navigable tree and enable
+clients to play media, get album art, obtain theme resources for the UI, and
+invoke app-specific actions.</p>
+
+<p>You don’t have to create a new app for Android Auto: you can extend your existing
+Android app with implementations of the media service interfaces. Your service exposes
+your app’s media content, theme resources, and app-specific actions using the methods and
+data types specified by the media service interfaces. This simplifies the development
+cycle because:</p>
+
+<ul>
+<li>You do not have to maintain a separate project for Android Auto</li>
+<li>You can reuse existing functionality from your Android app</li>
+</ul>
+
+<p>The Android Auto client presents the customized UI to users and invokes the
+functionality from your service as needed. This has two additional advantages:</p>
+
+<ul>
+<li>Your app does not implement a UI for Android Auto</li>
+<li>Your app does not manage user interactions directly</li>
+</ul>
+
+<p>This also means that you do not have to worry about vehicle-specific hardware
+differences such as screen resolutions, software interfaces, knobs and touch
+controls.</p>
+
+
+<h2 id="emulator">Testing Your App on an Android Device</h2>
+
+<p>The Android Auto SDK includes an APK with a media client implementation, which is
+similar to those available in compatible vehicles. To test your app with this
+client:</p>
+
+<ol>
+<li>Get an Android device with a similar form factor to a dashboard screen (like a
+Nexus 7).</li>
+<li>Configure the device for Android development.</li>
+<li>Install the APK for the media client from the Android Auto SDK on the device.</li>
+<li>Install the APK for your app on the device.</li>
+<li>Open the media client app from the Android Auto SDK on the device.</li>
+<li>Select your app from the list of available services.</li>
+</ol>
+
+<p>The customized UI for your app appears on the client. You can navigate the content
+library and play media. If your app provides app-specific actions, these actions appear
+in the UI controls.</p>
+
+
+<h2 id="running">Running Your App on Android Auto</h2>
+
+<p>Media apps are available on the Google Play Store for compatible Android devices.
+When users connect their Android device to a compatible vehicle, the
+Android Auto media client shows a list of all the Android apps installed on the phone
+that implement the media service interfaces.</p>
+
+<p>When users select one of these apps, the Android Auto media client uses the app’s
+service to respond to user input and invoke the methods in the media service interfaces
+to build the UI, navigate the content library, and play media.</p>
+
+<div style="margin-bottom:40px"> </div>
+</div>
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/design/auto/index.jd b/docs/html/design/auto/index.jd
new file mode 100644
index 0000000..c970cac
--- /dev/null
+++ b/docs/html/design/auto/index.jd
@@ -0,0 +1,35 @@
+page.title=Android Auto
+@jd:body
+
+<style>
+.auto-img-container-cols {
+  position:relative;
+  margin-bottom:25px;
+  margin-top:25px;
+}
+.auto-img-frame-cols {
+  z-index:2;
+  position:relative;
+}
+.auto-img-shot-cols {
+  position:absolute;
+  top:5px;
+  left:2px;
+  z-index:1;
+}
+</style>
+
+<div class="auto-img-container-cols" style="float:right; margin:0 0 40px 40px;width:460px">
+  <img class="auto-img-frame-cols" src="/auto/images/assets/00_frame.png">
+  <img class="auto-img-shot-cols" src="/auto/images/assets/03_a_musict.png">
+</div>
+
+<p>Android Auto is <strong>coming soon</strong> and brings apps to the car,
+integrating with the vehicle's input controls and display.</p>
+
+<p>The future design guidelines provide templates that define the user interaction model for all apps and let you hook into a standard UI with touch and voice controls. The templates meet international best practices for reducing driver distraction while still letting you customize and brand them to properly deliver your content.</p>
+
+<p><a href="{@docRoot}auto/index.html">Learn more about Android Auto</a>.</p>
+
+
+
diff --git a/docs/html/design/design_toc.cs b/docs/html/design/design_toc.cs
index 4c2aab2..885f336 100644
--- a/docs/html/design/design_toc.cs
+++ b/docs/html/design/design_toc.cs
@@ -5,7 +5,26 @@
     <ul>
       <li><a href="<?cs var:toroot ?>design/get-started/creative-vision.html">Creative Vision</a></li>
       <li><a href="<?cs var:toroot ?>design/get-started/principles.html">Design Principles</a></li>
-      <li><a href="<?cs var:toroot ?>design/get-started/ui-overview.html">UI Overview</a></li>
+    </ul>
+  </li>
+
+  <li class="nav-section">
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>design/devices.html">Devices</a></div>
+    <ul>
+      <li><a href="<?cs var:toroot ?>design/handhelds/index.html">Phones &amp; Tablets</a></li>
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>design/wear/index.html">Wear</a></div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>design/wear/creative-vision.html">Creative Vision</a></li>
+          <li><a href="<?cs var:toroot ?>design/wear/principles.html">Design Principles</a></li>
+          <li><a href="<?cs var:toroot ?>design/wear/structure.html">App Structure</a></li>
+          <li><a href="<?cs var:toroot ?>design/wear/patterns.html">UI Patterns</a></li>
+          <li><a href="<?cs var:toroot ?>design/wear/style.html">Style</a></li>
+        </ul>
+      </li>
+      <li><a href="<?cs var:toroot ?>design/tv/index.html">TV</a></li>
+      <li><a href="<?cs var:toroot ?>design/auto/index.html">Auto</a></li>
     </ul>
   </li>
 
diff --git a/docs/html/design/devices.jd b/docs/html/design/devices.jd
new file mode 100644
index 0000000..c67e585
--- /dev/null
+++ b/docs/html/design/devices.jd
@@ -0,0 +1,37 @@
+page.title=Devices
+page.viewport_width=970
+section.landing=true
+header.hide=1
+footer.hide=1
+@jd:body
+
+<style>
+#landing-graphic-container {
+  position: relative;
+}
+
+#text-overlay {
+  position: absolute;
+  left: 0;
+  top: 410px;
+  width: 340px;
+
+}
+#hero-image {
+}
+</style>
+
+<div id="landing-graphic-container">
+  <div id="text-overlay">
+    <p itemprop="description">The device-centric UI principles, overviews, and detailed guidelines
+      described here build on the core <a href="{@docRoot}design/get-started/principles.html">Android Design Principles</a>
+      to provide more specific design guidance for different form factors.
+    </p>
+    <p>
+    <a href="{@docRoot}design/handhelds/index.html" class="landing-page-link">Phones &amp; Tablets</a></p>
+  </div>
+  <a id="hero-image" href="{@docRoot}design/handhelds/index.html">
+    <img src="{@docRoot}design/media/device_family.png">
+  </a>
+</div>
+
diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd
index ddeda5c..eec4e8d 100644
--- a/docs/html/design/downloads/index.jd
+++ b/docs/html/design/downloads/index.jd
@@ -2,27 +2,12 @@
 page.tags=Icons,stencils,color swatches
 @jd:body
 
-<div class="layout-content-row">
-  <div class="layout-content-col span-9">
 
-<p>Want everything? We've bundled all the downloads available on Android Design, except for the
-  <a href="#roboto">Roboto</a> font family, into a single ZIP file. You can also download
-  individual files listed below.</p>
+<p>You may use these materials without restriction to facilitate your app design
+and implementation.</p>
 
-<p>You may use these materials without restriction in your apps and to develop your apps.</p>
 
-  </div>
-  <div class="layout-content-col span-4">
-
-<p>
-  <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'All Design Assets']);"
-    href="{@docRoot}downloads/design/Android_Design_Downloads_20131106.zip">Download All</a>
-</p>
-
-  </div>
-</div>
-
-<h2 id="stencils">Stencils and Sources</h2>
+<h2 id="stencils">Phone &amp; Tablet Stencils</h2>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-5">
@@ -54,6 +39,11 @@
   </div>
 </div>
 
+
+
+
+
+
 <h2 id="action-bar-icon-pack">Action Bar Icon Pack</h2>
 
 <div class="layout-content-row">
@@ -81,6 +71,77 @@
   </div>
 </div>
 
+
+
+
+
+<h2 id="Wear">Android Wear Materials</h2>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+  <h4>UI toolkit</h4>
+<p>The toolkit contains detailed specs and measurements of all of the primary Android Wear UI components. Available in PDF and Illustrator formats.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/downloads_wear_toolkit.png" width="220">
+
+  </div>
+  <div class="layout-content-col span-4">
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Toolkit AI']);"
+    href="{@docRoot}downloads/design/Android_Wear_Toolkit_20140626.ai">Adobe&reg; Illustrator&reg; Toolkit</a>
+
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Toolkit PDF']);"
+    href="{@docRoot}downloads/design/Android_Wear_Toolkit_20140626.pdf">PDF Toolkit</a>
+  </div>
+</div>
+
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+  <h4>Sample user flow patterns</h4>
+<p>Examples of how to chain together simple Android Wear UI components into common user flow patterns, from simple notifications to complex interactions involving full screen activities.
+</p>
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/downloads_wear_flows.png" width="220">
+
+  </div>
+  <div class="layout-content-col span-4">
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear App Patterns AI']);"
+    href="{@docRoot}downloads/design/Android_Wear_Patterns_20140626.ai">Adobe&reg; Illustrator&reg; App Patterns</a>
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear App Patterns PDF']);"
+    href="{@docRoot}downloads/design/Android_Wear_Patterns_20140626.pdf">PDF App Patterns</a>
+  </div>
+</div>
+
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-5">
+  <h4>Sample app design mocks</h4>
+<p>Stream cards and UI elements for some example apps in fully editable PSD format.
+</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/downloads_wear_psds.png" width="220">
+
+  </div>
+  <div class="layout-content-col span-4">
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Sample PSD Mocks']);"
+    href="{@docRoot}downloads/design/Android_Wear_Sample_Assets.zip">Adobe&reg; Photoshop&reg; mocks</a>
+  </div>
+</div>
+
+
+
+
+
+
+
 <h2 id="style">Style</h2>
 
 <div class="layout-content-row">
@@ -89,6 +150,7 @@
 <h4 id="roboto">Roboto</h4>
 <p>Ice Cream Sandwich introduced a new type family named Roboto, created specifically for the
 requirements of UI and high-resolution screens.</p>
+<p>For Android Wear, Roboto Condensed is the system font and the Regular and Light variants should be used by all Wear apps.</p>
 <p><a href="{@docRoot}design/style/typography.html">More on Roboto</a></p>
 <p><a href="http://www.google.com/fonts/specimen/Roboto" class="external-link">Roboto on Google Fonts</a></p>
 <p><a href="http://www.google.com/fonts/specimen/Roboto+Condensed" class="external-link">Roboto Condensed on Google Fonts</a></p>
diff --git a/docs/html/design/get-started/creative-vision.jd b/docs/html/design/get-started/creative-vision.jd
index 1ce305a..9261c6e 100644
--- a/docs/html/design/get-started/creative-vision.jd
+++ b/docs/html/design/get-started/creative-vision.jd
@@ -6,10 +6,10 @@
 <div class="vspace size-1">&nbsp;</div>
 
 <p itemprop="description">
-  We focused the design of Android around three overarching goals, which apply
-  to our core apps as well as the system at large. As you design apps to work
-  with Android, consider these goals: <em>Enchant me</em>, <em>Simplify my
-  life</em>, and <em>Make me amazing</em>
+  Starting with Ice Cream Sandwich, we focused the design of
+  Android around these three overarching goals, which apply
+  to our core apps as well as the system at large.
+  As you work with Android, consider these goals.
 </p>
 
 <div class="vspace size-1">&nbsp;</div>
diff --git a/docs/html/design/get-started/principles.jd b/docs/html/design/get-started/principles.jd
index 0b7147b..73ec3a6 100644
--- a/docs/html/design/get-started/principles.jd
+++ b/docs/html/design/get-started/principles.jd
@@ -1,9 +1,16 @@
-page.title=Design Principles
+page.title=Android Design Principles
 @jd:body
 
-<p>These design principles were developed by and for the Android User Experience Team to keep users'
-best interests in mind. Consider them as you apply your own creativity and design thinking. Deviate
-with purpose.</p>
+<p>These design principles were developed by and for the Android
+User Experience Team to keep users' best interests in mind.
+For Android developers and designers, they continue to
+underlie the more  detailed design guidelines for different
+types of devices.</p>
+
+<p>
+Consider these principles as you apply your own
+creativity and design thinking. Deviate with purpose.
+</p>
 
 <h2 id="enchant-me">Enchant Me</h2>
 
diff --git a/docs/html/design/get-started/ui-overview.jd b/docs/html/design/get-started/ui-overview.jd
deleted file mode 100644
index 5f4c40f..0000000
--- a/docs/html/design/get-started/ui-overview.jd
+++ /dev/null
@@ -1,141 +0,0 @@
-page.title=UI Overview
-@jd:body
-
-<p>Android's system UI provides the framework on top of which you build your app. Important aspects
-include the Home screen experience, global device navigation, and notifications.</p>
-<p>Your app will play an important part in keeping the overall Android experience consistent and
-enjoyable to use. At the end of this chapter we introduce the main elements for achieving this goal
-in your app.</p>
-<p>Read on for a quick overview of the most important aspects of the Android user interface.</p>
-
-<h2 id="home-all-apps-recents">Home, All Apps, and Recents</h2>
-
-<div class="vspace size-1">&nbsp;</div>
-
-<div class="layout-content-row">
-  <div class="layout-content-col span-4">
-
-    <img src="{@docRoot}design/media/ui_overview_home_screen.png">
-
-<h4>Home screen</h4>
-<p>Home is a customizable space that houses app shortcuts, folders and widgets. Navigate between
-different home screen panels by swiping left and right.</p>
-<p>The Favorites Tray at the bottom always keeps your most important shortcuts and folders in view
-regardless of which panel is currently showing.</p>
-<p>Access the entire collection of apps and widgets by touching the All Apps button at the center of
-the Favorites Tray.</p>
-
-  </div>
-  <div class="layout-content-col span-4">
-
-    <img src="{@docRoot}design/media/ui_overview_all_apps.png">
-
-<h4>All apps screen</h4>
-<p>The All Apps screen lets you browse the entire set of apps and widgets that are installed on your
-device.</p>
-<p>Users can drag an app or widget icon from the All Apps screen and place it in any empty location on
-any Home screen.</p>
-
-  </div>
-  <div class="layout-content-col span-4">
-
-    <img src="{@docRoot}design/media/ui_overview_recents.png">
-
-<h4>Recents screen</h4>
-<p>Recents provides an efficient way of switching between recently used applications. It provides a
-clear navigation path between multiple ongoing tasks.</p>
-<p>The Recents button at the right side of the navigation bar displays the apps that the user has
-interacted with most recently. They are organized in reverse chronological order with the most
-recently used app at the bottom.</p>
-<p>Switch to an app by touching it. Remove an item by swiping left or right.</p>
-
-  </div>
-</div>
-
-<h2 id="system-bars">System Bars</h2>
-
-<p>The system bars are screen areas dedicated to the display of notifications, communication of device
-status, and device navigation. Typically the system bars are displayed concurrently with your app.
-Apps that display immersive content, such as movies or images, can temporarily hide the system bars
-to allow the user to enjoy full screen content without distraction.</p>
-
-<img src="{@docRoot}design/media/ui_overview_system_ui.png">
-
-<div class="with-callouts">
-
-<ol>
-<li>
-<h4>Status Bar</h4>
-<p>Displays pending notifications on the left and status, such as time, battery level, or signal
-  strength, on the right. Swipe down from the status bar to show notification details.</p>
-</li>
-<li>
-<h4>Navigation Bar</h4>
-<p>New for phones in Android 4.0, the navigation bar is present only on devices that don't have
-  the traditional hardware keys. It houses the device navigation controls Back, Home, and
-  Recents, and also displays a menu for apps written for Android 2.3 or earlier.</p>
-</li>
-</ol>
-
-</div>
-
-<h2 id="notifications">Notifications</h2>
-
-<p>Notifications are brief messages that users can access at any time from the status bar. They
-provide updates, reminders, or information that's important, but not critical enough to warrant
-interrupting the user. Open the notifications drawer by swiping down on the status bar. Touching a
-notification opens the associated app. <a href="{@docRoot}design/patterns/notifications.html">More on Notifications</a></p>
-
-<div class="layout-content-row">
-  <div class="layout-content-col span-4">
-
-    <img src="{@docRoot}design/media/ui_overview_notifications.png">
-
-  </div>
-  <div class="layout-content-col span-9">
-
-    <img src="{@docRoot}design/media/notifications_dismiss.png">
-
-<p>Notifications can be expanded to uncover more details and relevant actions. When collapsed, notifications
-  have a one-line title and a one-line message.The recommended layout for a notification includes two lines.
-  If necessary, you can add a third line.</p>
-<p>Swiping a notification right or left removes it from the notification drawer.</p>
-
-  </div>
-</div>
-
-
-<h2 id="app">Common App UI</h2>
-
-
-<div class="layout-content-row">
-  <div class="layout-content-col span-7">
-
-    <img src="{@docRoot}design/media/app_structure_drawer.png">
-
-  </div>
-  <div class="layout-content-col span-6 with-callouts">
-
-<p>A typical Android app uses action bars, and many apps will include a navigation drawer.</p>
-<ol>
-<li>
-<h4>Action Bar</h4>
-<p>The command and control center for your app. The action bar surfaces the most important actions
-  for the current view, and may include simple controls for switching between views.</p>
-<p><a href="{@docRoot}design/patterns/actionbar.html">More on the Action Bar</a></p>
-</li>
-<li>
-<h4>Navigation Drawer</h4>
-<p>If your app's structure is more complex, the navigation drawer can display the main navigation
-  options. The navigation drawer expands from the left edge of the screen, overlaying the content
-  area but not the action bar.</p>
-<p><a href="{@docRoot}design/patterns/navigation-drawer.html">More on the Navigation Drawer</a></p>
-</li>
-<li>
-<h4>Content Area</h4>
-<p>The space where the content of your app is displayed.</p>
-</li>
-</ol>
-
-  </div>
-</div>
diff --git a/docs/html/design/handhelds/index.jd b/docs/html/design/handhelds/index.jd
new file mode 100644
index 0000000..882b070
--- /dev/null
+++ b/docs/html/design/handhelds/index.jd
@@ -0,0 +1,149 @@
+page.title=Phones &amp; Tablets
+@jd:body
+
+<p>
+Android's system UI provides the framework on top of which you build your app,
+whether you're designing for phones, tablets, watches, or other form factors.
+Aspects of UI that are especially important for phones and tablets include
+the Home screen experience, global device navigation, and notifications.
+</p>
+
+<p>
+Your app will play an important part in keeping the overall Android experience
+consistent and enjoyable to use. This page introduces some of the main elements
+that can help you achieve this goal. The main Android Design topics listed on
+the left, after the Devices sections, provide detailed guidelines for phones
+and tablets.
+</p>
+
+<h2 id="home-all-apps-recents">Home, All Apps, and Recents</h2>
+
+<div class="vspace size-1">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/ui_overview_home_screen.png">
+
+<h4>Home screen</h4>
+<p>Home is a customizable space that houses app shortcuts, folders and widgets. Navigate between
+different home screen panels by swiping left and right.</p>
+<p>The Favorites Tray at the bottom always keeps your most important shortcuts and folders in view
+regardless of which panel is currently showing.</p>
+<p>Access the entire collection of apps and widgets by touching the All Apps button at the center of
+the Favorites Tray.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/ui_overview_all_apps.png">
+
+<h4>All apps screen</h4>
+<p>The All Apps screen lets you browse the entire set of apps and widgets that are installed on your
+device.</p>
+<p>Users can drag an app or widget icon from the All Apps screen and place it in any empty location on
+any Home screen.</p>
+
+  </div>
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/ui_overview_recents.png">
+
+<h4>Recents screen</h4>
+<p>Recents provides an efficient way of switching between recently used applications. It provides a
+clear navigation path between multiple ongoing tasks.</p>
+<p>The Recents button at the right side of the navigation bar displays the apps that the user has
+interacted with most recently. They are organized in reverse chronological order with the most
+recently used app at the bottom.</p>
+<p>Switch to an app by touching it. Remove an item by swiping left or right.</p>
+
+  </div>
+</div>
+
+<h2 id="system-bars">System Bars</h2>
+
+<p>The system bars are screen areas dedicated to the display of notifications, communication of device
+status, and device navigation. Typically the system bars are displayed concurrently with your app.
+Apps that display immersive content, such as movies or images, can temporarily hide the system bars
+to allow the user to enjoy full screen content without distraction.</p>
+
+<img src="{@docRoot}design/media/ui_overview_system_ui.png">
+
+<div class="with-callouts">
+
+<ol>
+<li>
+<h4>Status Bar</h4>
+<p>Displays pending notifications on the left and status, such as time, battery level, or signal
+  strength, on the right. Swipe down from the status bar to show notification details.</p>
+</li>
+<li>
+<h4>Navigation Bar</h4>
+<p>New for phones in Android 4.0, the navigation bar is present only on devices that don't have
+  the traditional hardware keys. It houses the device navigation controls Back, Home, and
+  Recents, and also displays a menu for apps written for Android 2.3 or earlier.</p>
+</li>
+</ol>
+
+</div>
+
+<h2 id="notifications">Notifications</h2>
+
+<p>Notifications are brief messages that users can access at any time from the status bar. They
+provide updates, reminders, or information that's important, but not critical enough to warrant
+interrupting the user. Open the notifications drawer by swiping down on the status bar. Touching a
+notification opens the associated app. <a href="{@docRoot}design/patterns/notifications.html">More on Notifications</a></p>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-4">
+
+    <img src="{@docRoot}design/media/ui_overview_notifications.png">
+
+  </div>
+  <div class="layout-content-col span-9">
+
+    <img src="{@docRoot}design/media/notifications_dismiss.png">
+
+<p>Notifications can be expanded to uncover more details and relevant actions. When collapsed, notifications
+  have a one-line title and a one-line message.The recommended layout for a notification includes two lines.
+  If necessary, you can add a third line.</p>
+<p>Swiping a notification right or left removes it from the notification drawer.</p>
+
+  </div>
+</div>
+
+
+<h2 id="app">Common App UI</h2>
+
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+
+    <img src="{@docRoot}design/media/app_structure_drawer.png">
+
+  </div>
+  <div class="layout-content-col span-6 with-callouts">
+
+<p>A typical Android app uses action bars, and many apps will include a navigation drawer.</p>
+<ol>
+<li>
+<h4>Action Bar</h4>
+<p>The command and control center for your app. The action bar surfaces the most important actions
+  for the current view, and may include simple controls for switching between views.</p>
+<p><a href="{@docRoot}design/patterns/actionbar.html">More on the Action Bar</a></p>
+</li>
+<li>
+<h4>Navigation Drawer</h4>
+<p>If your app's structure is more complex, the navigation drawer can display the main navigation
+  options. The navigation drawer expands from the left edge of the screen, overlaying the content
+  area but not the action bar.</p>
+<p><a href="{@docRoot}design/patterns/navigation-drawer.html">More on the Navigation Drawer</a></p>
+</li>
+<li>
+<h4>Content Area</h4>
+<p>The space where the content of your app is displayed.</p>
+</li>
+</ol>
+
+  </div>
+</div>
diff --git a/docs/html/design/index.jd b/docs/html/design/index.jd
index cb7dd4f..27e3169 100644
--- a/docs/html/design/index.jd
+++ b/docs/html/design/index.jd
@@ -13,7 +13,7 @@
 
 #text-overlay {
   position: absolute;
-  left: 36px;
+  left: 0;
   top: 42px;
   width: 266px;
 
@@ -34,5 +34,15 @@
   <a id="hero-image" href="/design/get-started/creative-vision.html">
     <img src="/design/media/index_landing_page.png">
   </a>
+
+<div style="background: hsl(8, 70%, 54%); margin: 0; padding: 20px 20px 10px 20px;color: #fff; position: absolute;top: 255px;width: 179px;">
+<h2 style="color: #fff;margin:0 0 10px; font-size:18px" class="norule">L Developer Preview</h2>
+<p> The next version of Android uses a design
+metaphor inspired by paper and ink that provides a reassuring sense of tactility. Before it arrives for users, you can get an early
+look at the new Material design.
+</p>
+<p><a class="white" href="{@docRoot}preview/material/index.html">Learn more about Material</a></p>
+</div>
+
 </div>
 
diff --git a/docs/html/design/media/android-tv.png b/docs/html/design/media/android-tv.png
new file mode 100644
index 0000000..98005cd
--- /dev/null
+++ b/docs/html/design/media/android-tv.png
Binary files differ
diff --git a/docs/html/design/media/device_family.png b/docs/html/design/media/device_family.png
new file mode 100644
index 0000000..96b31d3
--- /dev/null
+++ b/docs/html/design/media/device_family.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_flows.png b/docs/html/design/media/downloads_wear_flows.png
new file mode 100644
index 0000000..5669a2d
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_flows.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_psds.png b/docs/html/design/media/downloads_wear_psds.png
new file mode 100644
index 0000000..2b071a3
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_psds.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_toolkit.png b/docs/html/design/media/downloads_wear_toolkit.png
new file mode 100644
index 0000000..c3d30e8
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_toolkit.png
Binary files differ
diff --git a/docs/html/design/media/wear/1D_picker.png b/docs/html/design/media/wear/1D_picker.png
new file mode 100644
index 0000000..46c6bf6
--- /dev/null
+++ b/docs/html/design/media/wear/1D_picker.png
Binary files differ
diff --git a/docs/html/design/media/wear/2D_picker.png b/docs/html/design/media/wear/2D_picker.png
new file mode 100644
index 0000000..82c766a
--- /dev/null
+++ b/docs/html/design/media/wear/2D_picker.png
Binary files differ
diff --git a/docs/html/design/media/wear/2D_picker_action.png b/docs/html/design/media/wear/2D_picker_action.png
new file mode 100644
index 0000000..8560ef8
--- /dev/null
+++ b/docs/html/design/media/wear/2D_picker_action.png
Binary files differ
diff --git a/docs/html/design/media/wear/2d_picker_indicated.png b/docs/html/design/media/wear/2d_picker_indicated.png
new file mode 100644
index 0000000..d31c099
--- /dev/null
+++ b/docs/html/design/media/wear/2d_picker_indicated.png
Binary files differ
diff --git a/docs/html/design/media/wear/Bluebird.png b/docs/html/design/media/wear/Bluebird.png
new file mode 100644
index 0000000..7b911c7
--- /dev/null
+++ b/docs/html/design/media/wear/Bluebird.png
Binary files differ
diff --git a/docs/html/design/media/wear/Dismissing-cards.png b/docs/html/design/media/wear/Dismissing-cards.png
new file mode 100644
index 0000000..973ef39
--- /dev/null
+++ b/docs/html/design/media/wear/Dismissing-cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/action_button.png b/docs/html/design/media/wear/action_button.png
new file mode 100644
index 0000000..dfdffa3
--- /dev/null
+++ b/docs/html/design/media/wear/action_button.png
Binary files differ
diff --git a/docs/html/design/media/wear/action_on_card.png b/docs/html/design/media/wear/action_on_card.png
new file mode 100644
index 0000000..d0b0fff
--- /dev/null
+++ b/docs/html/design/media/wear/action_on_card.png
Binary files differ
diff --git a/docs/html/design/media/wear/appstructuresample.png b/docs/html/design/media/wear/appstructuresample.png
new file mode 100644
index 0000000..0e5b86f
--- /dev/null
+++ b/docs/html/design/media/wear/appstructuresample.png
Binary files differ
diff --git a/docs/html/design/media/wear/assets_specifics.png b/docs/html/design/media/wear/assets_specifics.png
new file mode 100644
index 0000000..00f97d1
--- /dev/null
+++ b/docs/html/design/media/wear/assets_specifics.png
Binary files differ
diff --git a/docs/html/design/media/wear/biggesture.png b/docs/html/design/media/wear/biggesture.png
new file mode 100644
index 0000000..6c6f0cd
--- /dev/null
+++ b/docs/html/design/media/wear/biggesture.png
Binary files differ
diff --git a/docs/html/design/media/wear/bridgednotifications.jpg b/docs/html/design/media/wear/bridgednotifications.jpg
new file mode 100644
index 0000000..a9e57a4
--- /dev/null
+++ b/docs/html/design/media/wear/bridgednotifications.jpg
Binary files differ
diff --git a/docs/html/wear/images/circle_message2.png b/docs/html/design/media/wear/circle_message2.png
similarity index 100%
rename from docs/html/wear/images/circle_message2.png
rename to docs/html/design/media/wear/circle_message2.png
Binary files differ
diff --git a/docs/html/design/media/wear/clear_bold_type.jpg b/docs/html/design/media/wear/clear_bold_type.jpg
new file mode 100644
index 0000000..e4b742c
--- /dev/null
+++ b/docs/html/design/media/wear/clear_bold_type.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/confirmation.png b/docs/html/design/media/wear/confirmation.png
new file mode 100644
index 0000000..513b85f
--- /dev/null
+++ b/docs/html/design/media/wear/confirmation.png
Binary files differ
diff --git a/docs/html/wear/images/fitness-24.png b/docs/html/design/media/wear/context_fitness.png
similarity index 100%
rename from docs/html/wear/images/fitness-24.png
rename to docs/html/design/media/wear/context_fitness.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_lights.png b/docs/html/design/media/wear/context_lights.png
new file mode 100644
index 0000000..6374bdd
--- /dev/null
+++ b/docs/html/design/media/wear/context_lights.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_running.png b/docs/html/design/media/wear/context_running.png
new file mode 100644
index 0000000..8b44e8c
--- /dev/null
+++ b/docs/html/design/media/wear/context_running.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_workout.png b/docs/html/design/media/wear/context_workout.png
new file mode 100644
index 0000000..2ed8901
--- /dev/null
+++ b/docs/html/design/media/wear/context_workout.png
Binary files differ
diff --git a/docs/html/design/media/wear/continue_phone.png b/docs/html/design/media/wear/continue_phone.png
new file mode 100644
index 0000000..fed93b6
--- /dev/null
+++ b/docs/html/design/media/wear/continue_phone.png
Binary files differ
diff --git a/docs/html/design/media/wear/copywrite.png b/docs/html/design/media/wear/copywrite.png
new file mode 100644
index 0000000..78be0bd
--- /dev/null
+++ b/docs/html/design/media/wear/copywrite.png
Binary files differ
diff --git a/docs/html/design/media/wear/countdown.png b/docs/html/design/media/wear/countdown.png
new file mode 100644
index 0000000..11b1504
--- /dev/null
+++ b/docs/html/design/media/wear/countdown.png
Binary files differ
diff --git a/docs/html/design/media/wear/customlayout.jpg b/docs/html/design/media/wear/customlayout.jpg
new file mode 100644
index 0000000..9573cfc
--- /dev/null
+++ b/docs/html/design/media/wear/customlayout.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/customlayout.png b/docs/html/design/media/wear/customlayout.png
new file mode 100644
index 0000000..6a3cd9a
--- /dev/null
+++ b/docs/html/design/media/wear/customlayout.png
Binary files differ
diff --git a/docs/html/design/media/wear/dismiss_cards.png b/docs/html/design/media/wear/dismiss_cards.png
new file mode 100644
index 0000000..2e2d53b8
--- /dev/null
+++ b/docs/html/design/media/wear/dismiss_cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/expandable_stacks.png b/docs/html/design/media/wear/expandable_stacks.png
new file mode 100644
index 0000000..edc2456
--- /dev/null
+++ b/docs/html/design/media/wear/expandable_stacks.png
Binary files differ
diff --git a/docs/html/design/media/wear/fitness.png b/docs/html/design/media/wear/fitness.png
new file mode 100644
index 0000000..18ae969
--- /dev/null
+++ b/docs/html/design/media/wear/fitness.png
Binary files differ
diff --git a/docs/html/design/media/wear/five_seconds.gif b/docs/html/design/media/wear/five_seconds.gif
new file mode 100644
index 0000000..199ceb9
--- /dev/null
+++ b/docs/html/design/media/wear/five_seconds.gif
Binary files differ
diff --git a/docs/html/design/media/wear/gross_gestures.png b/docs/html/design/media/wear/gross_gestures.png
new file mode 100644
index 0000000..1faad9a
--- /dev/null
+++ b/docs/html/design/media/wear/gross_gestures.png
Binary files differ
diff --git a/docs/html/design/media/wear/low_info_card.png b/docs/html/design/media/wear/low_info_card.png
new file mode 100644
index 0000000..a3ebf16
--- /dev/null
+++ b/docs/html/design/media/wear/low_info_card.png
Binary files differ
diff --git a/docs/html/design/media/wear/peek-card.png b/docs/html/design/media/wear/peek-card.png
new file mode 100644
index 0000000..916f157
--- /dev/null
+++ b/docs/html/design/media/wear/peek-card.png
Binary files differ
diff --git a/docs/html/design/media/wear/round_noframe.png b/docs/html/design/media/wear/round_noframe.png
new file mode 100644
index 0000000..7a9759b
--- /dev/null
+++ b/docs/html/design/media/wear/round_noframe.png
Binary files differ
diff --git a/docs/html/design/media/wear/selection_list.png b/docs/html/design/media/wear/selection_list.png
new file mode 100644
index 0000000..dcb0745
--- /dev/null
+++ b/docs/html/design/media/wear/selection_list.png
Binary files differ
diff --git a/docs/html/design/media/wear/separate-info-cards.png b/docs/html/design/media/wear/separate-info-cards.png
new file mode 100644
index 0000000..59ee084
--- /dev/null
+++ b/docs/html/design/media/wear/separate-info-cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards.jpg b/docs/html/design/media/wear/separate_info_cards.jpg
new file mode 100644
index 0000000..d4cb386
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards_1.jpg b/docs/html/design/media/wear/separate_info_cards_1.jpg
new file mode 100644
index 0000000..b987aea
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards_1.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards_2.jpg b/docs/html/design/media/wear/separate_info_cards_2.jpg
new file mode 100644
index 0000000..1930cb8
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards_2.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/single_action_controls.jpg b/docs/html/design/media/wear/single_action_controls.jpg
new file mode 100644
index 0000000..ef89da0
--- /dev/null
+++ b/docs/html/design/media/wear/single_action_controls.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/vision_music.png b/docs/html/design/media/wear/vision_music.png
new file mode 100644
index 0000000..b676d20
--- /dev/null
+++ b/docs/html/design/media/wear/vision_music.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_navigation.png b/docs/html/design/media/wear/vision_navigation.png
new file mode 100644
index 0000000..ef56977
--- /dev/null
+++ b/docs/html/design/media/wear/vision_navigation.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_traffic.png b/docs/html/design/media/wear/vision_traffic.png
new file mode 100644
index 0000000..2affa22
--- /dev/null
+++ b/docs/html/design/media/wear/vision_traffic.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_voice.png b/docs/html/design/media/wear/vision_voice.png
new file mode 100644
index 0000000..25fd49e
--- /dev/null
+++ b/docs/html/design/media/wear/vision_voice.png
Binary files differ
diff --git a/docs/html/design/media/wear/voice_commands.png b/docs/html/design/media/wear/voice_commands.png
new file mode 100644
index 0000000..9839ed8
--- /dev/null
+++ b/docs/html/design/media/wear/voice_commands.png
Binary files differ
diff --git a/docs/html/design/media/wear/wear_checkin.png b/docs/html/design/media/wear/wear_checkin.png
new file mode 100644
index 0000000..9aeb267
--- /dev/null
+++ b/docs/html/design/media/wear/wear_checkin.png
Binary files differ
diff --git a/docs/html/design/tv/index.jd b/docs/html/design/tv/index.jd
new file mode 100644
index 0000000..d144ff0
--- /dev/null
+++ b/docs/html/design/tv/index.jd
@@ -0,0 +1,15 @@
+page.title=Android TV
+@jd:body
+
+
+<img src="{@docRoot}design/media/android-tv.png"
+  width="460" style="float:right;margin:0 0 40px 40px" /> 
+
+
+<p>Android TV is <strong>coming soon</strong> and lets you engage your users in a new, shared environment.</p>
+
+<p>Users bring a specific set of expectations to the experience of watching TV, versus interacting
+with a phone or tablet. So find out how to get your app ready for its big-screen debut
+later this year by reading the
+<a href="{@docRoot}preview/tv/design/index.html">Android TV Design Guide</a>
+in the L Developer Preview.</p>
\ No newline at end of file
diff --git a/docs/html/design/wear/creative-vision.jd b/docs/html/design/wear/creative-vision.jd
new file mode 100644
index 0000000..aee115c
--- /dev/null
+++ b/docs/html/design/wear/creative-vision.jd
@@ -0,0 +1,56 @@
+page.title=Creative Vision for Android Wear
+@jd:body
+
+<style>
+div.span-13 {
+  margin:10px 0;
+}
+div.span-13 img {
+  float:left;
+  margin:2px 20px 40px 0;
+}
+div.span-13 p {
+  margin-left:167px;
+}
+div.span-13 h2 {
+  margin-top:0;
+  }
+</style>
+
+<p>Android Wear devices provide just the right information at just the right time,
+allowing users to be more connected to both the virtual world and the real world. Great Android
+Wear experiences are:</p>
+
+
+  <div class="layout-content-col span-13">
+    <img src="{@docRoot}design/media/wear/vision_traffic.png" width="147" height="147" />
+
+    <h2 id="Launched">Launched automatically</h2>
+    <p>Most people are used to launching apps by clicking an icon. Android Wear is different. Wearable apps are aware of the user’s context - time, location, physical activity, and so on. The apps use this information to insert cards into the stream when they become relevant. This makes Android Wear timely, relevant and very specific.</p>
+  </div>
+
+  <div class="layout-content-col span-13">
+    <img src="{@docRoot}design/media/wear/vision_navigation.png" width="147" height="147" />
+
+    <h2 id="Glanceable">Glanceable</h2>
+    <p>A classic wrist watch is designed to let you see the time in a split second and get on with what you were doing. Designing for Android Wear is no different. The less time it takes to use your software, the more time the user can be present in whatever they are doing. Android wear is fast, sharp and immediate.</p>
+  </div>
+
+  <div class="layout-content-col span-13">
+    <img src="{@docRoot}design/media/wear/vision_voice.png" width="147" height="147" style="border: 1px solid #ddd;" />
+
+    <h2 id="SuggestDemand">All about suggest and demand</h2>
+    <p>Android Wear is like a great personal assistant: it knows you and your preferences, it only interrupts you when absolutely necessary, and it’s always on hand to provide a ready answer. Android Wear is helpful, respectful, and responsive.</p>
+  </div>
+
+  <div class="layout-content-col span-13">
+    <img src="{@docRoot}design/media/wear/vision_music.png" width="147" height="147" />
+
+    <h2 id="Interaction">Zero or low interaction</h2>
+    <p>Staying true to the strengths afforded by a smaller form factor, Android Wear focuses on simple interactions, only requiring input by the user when absolutely necessary. Most inputs are based around touch swipes or voice, and inputs requiring fine-grained finger movements are avoided. Android Wear is gestural, simple, and fast.</p>
+  </div>
+
+<p>By providing a smart connection to the rest of the world while respecting the user’s attention, Android Wear feels personal and global, simple and smart, unobtrusive and ever-ready. Applications that represent these principles will feel most at home in the overall Android Wear experience.</p>
+
+<p>Third party apps extend Android Wear to be more specialized and helpful throughout the day. Installing apps are a way for the user to tell the Android Wear how to do that.</p>
+
diff --git a/docs/html/design/wear/index.jd b/docs/html/design/wear/index.jd
new file mode 100644
index 0000000..d6202d1
--- /dev/null
+++ b/docs/html/design/wear/index.jd
@@ -0,0 +1,69 @@
+page.title=Android Wear
+@jd:body
+
+
+
+<p>Designing apps for wearable devices powered by Android Wear
+is substantially different than designing for phones or
+tablets: different strengths and weaknesses, different use cases, different ergonomics.
+To get started, you should understand the overall vision for the Android Wear experience,
+and how apps fit into and enhance this experience.</p>
+
+<p>A new form factor deserves a new UI model. At a high level, the Android Wear UI consists of two
+main spaces centered around the core functions of <strong>Suggest</strong> and
+<strong>Demand</strong>. Your app will have an important role to play in both of these
+spaces.</p>
+
+
+
+<h2 id="Stream">Suggest: The Context Stream</h2>
+
+<div class="framed-wear-square" style="float:right;margin:0 -22px 60px 40px">
+  <img src="{@docRoot}wear/images/screens/stream.gif">
+</div>
+
+<p>The context stream is a vertical list of cards, each showing a useful or timely piece of information. Much like the Google Now feature on Android phones and tablets, users swipe vertically to navigate from card to card. Only one card is displayed at a time, and background photos are used to provide additional visual information. Your application can create cards and inject them into the stream when they are most likely to be useful.</p>
+
+<p>This UI model ensures that users don’t have to launch many different applications to check for updates; they can simply glance at their stream for a brief update on what’s important to them.</p>
+
+<p>Cards in the stream are more than simple notifications. They can be swiped horizontally to reveal additional
+<a href="{@docRoot}design/wear/patterns.html#Pages">pages</a>. Further horizontal swiping may reveal
+<a href="{@docRoot}design/wear/patterns.html#Actions">buttons</a>, allowing the user to take action on the notification. Cards can also be dismissed by swiping left to right, removing them from the stream until the next time the app has useful information to display.</p>
+
+
+
+
+<h2 id="CueCard">Demand: The Cue Card</h2>
+
+<div class="framed-wear-square" style="float:right;margin:0 -22px 60px 40px">
+  <img src="{@docRoot}wear/images/screens/cuecard.gif">
+</div>
+
+<p>For cases where Android Wear does not suggest an answer proactively through the context stream, the cue card allows users to speak to Google. The cue card is opened by saying, “OK Google” or by tapping on the background of the home screen. Swiping up on the cue card shows a list of suggested voice commands, which can also be tapped.</p>
+
+<p>At a technical level, each suggested voice command activates a specific type of intent. As a developer, you can match your applications to some of these intents so that users can complete tasks using these voice commands. Multiple applications may register for a single voice intent, and the user will have the opportunity to choose which application they prefer to use.</p>
+
+<p>Applications can respond to a voice command in the same way as they can respond to a tap on a regular in-stream action button: by adding or updating a stream card, or by launching a full screen application. Voice input often takes the form of a command, such as "remind me to get milk," in which case a simple confirmation animation is sufficient to display before automatically returning to the Context Stream.</p>
+
+
+<h2 id="Other">Other UI Features</h2>
+
+<ul>
+<li>The <strong>Home screen</strong> is the default state of the device and features:
+  <ul>
+  <li>The background, showing either content relating to the first card or a custom watch face design, depending on the watch face the user has chosen. Tapping anywhere on the background or saying "Ok Google" starts a voice query.
+  <li>Status indicators, showing connectivity, charging status, airplane mode, and in some watch faces a count of unread items.
+  <li>The top ranked card in the Context Stream, peeking up at the bottom of the screen. The amount of the peek card that appears is determined by the current watch face.
+  </ul>
+</li>
+
+<li><strong>Watch faces</strong> may be chosen by the user to appear in the background of the Home screen. Watch faces display the time and accommodate the top ranked peek card. The user can choose a different watch face by long pressing on the current one.</li>
+
+<li>Some devices may enter a low-power <strong>Ambient Mode</strong> when not being used. This usually involves dimming the screen in some way. The contents of a peek card will automatically be optimized for display in this state. Users can exit ambient mode by tapping on the screen, by tilting the screen towards them, or by pressing a hardware button if one exists.</li>
+
+<li>Swiping down on the Home screen reveals the <strong>Date and Battery</strong> display. Dragging further down toggles <strong>Mute mode</strong>, preventing interruptive notifications from vibrating and illuminating the screen.</li>
+
+<li>The <strong>Settings screen</strong> can be invoked from the cue card or on some devices using a hardware button. From here the user may shut down or restart their device, adjust screen brightness, toggle airplane mode, and access device information.</li>
+
+<li><strong>Full screen apps</strong> can be launched on top of the main stream where a wider range of interaction is called for. Although not stylistically limited to the context stream pattern, apps should respect the same design principles as the rest of the system. For more information, see the <a href="{@docRoot}design/wear/structure.html">App Structure</a> guide.</li>
+</ul>
\ No newline at end of file
diff --git a/docs/html/design/wear/patterns.jd b/docs/html/design/wear/patterns.jd
new file mode 100644
index 0000000..274b62f
--- /dev/null
+++ b/docs/html/design/wear/patterns.jd
@@ -0,0 +1,149 @@
+page.title=UI Patterns for Android Wear
+@jd:body
+
+
+
+<p>Android Wear is used for micro-interactions, and so adhering to consistent design patterns that users are already accustomed to is paramount.</p>
+
+<h2 id="Cards">Cards</h2>
+
+<p>Cards in the stream can take slightly different forms:</p>
+
+  <img src="{@docRoot}design/media/wear/Bluebird.png" width="147" height="147" style="float:left;margin:0 0 20px 0px">
+
+
+  <img src="{@docRoot}design/media/wear/single_action_controls.jpg" width="147" height="147" style="float:left;margin:0 0 20px 40px">
+
+
+  <img src="{@docRoot}design/media/wear/expandable_stacks.png" width="147" height="147" style="float:left;margin:0 0 20px 40px">
+
+<ul style="clear:both">
+<li>Standard cards for displaying information from a notification</li>
+<li>Single-action controls (such as a play/pause toggle)</li>
+<li>Expandable stack of cards, for grouping a set of related notifications together</li>
+</ul>
+
+
+<h2 id="Icons">App icons</h2>
+
+  <img src="{@docRoot}design/media/wear/clear_bold_type.jpg" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>App icons appear in a fixed position overhanging the edge at the top right of the card by default for all notifications in the Context Stream. This is an opportunity for cards to be recognized as coming from a specific source. Photo backgrounds should be used only to convey information, not to brand a card. App icons are necessary only on the leftmost card; it is not necessary to add the app icon to pages.</p>
+
+<h2 id="Pages" style="clear:both">Pages</h2>
+
+  <img src="{@docRoot}design/media/wear/separate-info-cards.png" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Supplementary information should be displayed on additional cards to the right of a main Context Stream card. In most cases one additional detail card should be sufficient. For example, a weather card might show the weather for the current location today, with subsequent days listed in an additional card to the right. Keep the number of detail cards as low as possible. Actions (see below) should always come after pages; don’t change the order or interleave them.</p>
+
+
+<a class="notice-developers left" href="{@docRoot}training/wearables/notifications/pages.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>Adding Pages to a Notification</p>
+  </div>
+</a>
+
+<h2 id="Dismissing" style="clear:both">Dismissing cards</h2>
+
+  <img src="{@docRoot}design/media/wear/dismiss_cards.png" height="147">
+
+<p>Swiping from left to right on a card causes it to be dismissed from the stream. Dismissed cards may return when they next have relevant information. State is synced between the Android Wear context stream and the notifications on the Android handheld device, so dismissing from one causes an automatic dismissal from the other.</p>
+
+
+
+<h2 id="Actions" style="clear:both">Action buttons</h2>
+
+  <img src="{@docRoot}design/media/wear/action_button.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Where the user may need to take action on the information shown in a notification, you can provide action buttons. These are system-rendered buttons that appear to the right of detail cards. They consist of a white icon set on a blue system-rendered circular button and a short caption with a verb. Actions should be limited to three for a single card row.</p>
+
+<p>Tapping on an action button can cause an action to be executed; or an action to be continued on the companion handheld; or a full screen activity to be invoked for further input (see the <a href="#2DPicker">2D Picker</a> section below).</p>
+
+<p>Refer to the UI Toolkit provided in the <a href="{@docRoot}design/downloads/index.html#Wear">Downloads</a> page for detailed specs regarding action icons.</p>
+
+
+<h2 id="Countdown" style="clear:both">Action countdown and confirmation</h2>
+
+  <img src="{@docRoot}design/media/wear/countdown.png" width="149" height="149" style="float:right;margin:0 0 20px 40px">
+
+<p>Where tapping on an action button results in an action being executed, one of the following can happen:</p>
+
+<ol>
+<li>The action is completed immediately and the result of the action is shown (either by updating the relevant card contents immediately, or by showing a confirmation animation).</li>
+<li>A short countdown animation to completing the action is played, which the user can interrupt to cancel. Once the timer has counted down, a confirmation animation is played. This animation can be custom-designed by developers.</li>
+<li>A confirmation step is required. This is for actions that are potentially damaging if accidentally triggered. A generic confirmation template is supplied by the system. Once the user confirms, the standard confirmation animation is played.</li>
+<li>The cue card can be invoked to continue specifying the action. For example in a messaging application, tapping a “Reply” action button invokes the Cue Card and prompts for voice input. In this case the prompt label (such as “Speak your message…”) and a set of sample voice suggestions can be specified by developers.</li>
+</ol>
+
+
+<h2 id="Continuing" style="clear:both">Continuing activities on phone</h2>
+
+  <img src="{@docRoot}design/media/wear/continue_phone.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Developers should attempt to perform actions on the wearable device wherever possible. In cases where the phone must be used, a generic animation should be played once the action button has been tapped and the corresponding Android app will open on the phone.</p>
+
+
+<h2 id="ActionOnCard" style="clear:both">Actions on cards (such as media controls)</h2>
+
+  <img src="{@docRoot}design/media/wear/action_on_card.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Some cards may benefit from having tappable actions directly on a card. Some guidance on when to use this pattern versus using an action button:</p>
+
+<ul>
+<li>This pattern should be used when only one possible action could be reasonably expected. For example, tapping on an address with a car icon and ETA seems like it would very obviously launch directions. Conversely, if you see a contact's photo and name, it's not clear what tapping would do (call them? email them?), so the pattern shouldn't be used in this case.</li>
+<li>On-card actions should not require a text label to be understood.</li>
+<li>On-card actions should only result in something happening on the wearable (apart from web links to open them on the phone).</li>
+<li>Only one action per card: no menus on a single card.</li>
+</ul>
+
+<p>Good examples of using an action on card include: play / pause music; toggle light switch on and off; navigate to an address; call a phone number.</p>
+
+
+<h2 id="Stacks" style="clear:both">Card stacks</h2>
+  <img src="{@docRoot}design/media/wear/expandable_stacks.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+<p>Card stacks group related cards together and allow them to be progressively expanded vertically in the stream. A tap on a stack fans the cards out so that the top edge of each card can be seen. A subsequent tap on a fanned card reveals that card fully. Stacks of cards revert to a fully collapsed state once the user has swiped away from them.</p>
+
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/notifications/stacks.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>Stacking Notifications</p>
+  </div>
+</a>
+
+
+<h2 id="2DPicker" style="clear:both">2D Picker</h2>
+
+<p>A 2D Picker component in your app can be invoked from the cue card or from an action button. It allows users to choose from a list of items, and optionally select an attribute of each item. For example, in response to a voice action to “buy tickets to a movie tonight,” you could show a 2D Picker with a vertical list of movies playing, with each movie having a horizontal list of showtimes.</p>
+
+<img src="{@docRoot}design/media/wear/2D_picker_action.png" width="500" alt="">
+
+<p>In some instances, further information may be required. In these cases, the most probable default values for these choices should be chosen on the user’s behalf with the option to edit before completing the action. This pattern is in keeping with Android Wear’s core design principle of minimizing interactions required.</p>
+
+<p>More information about how to use the 2D Picker pattern is provided in the <a href="{@docRoot}design/wear/structure.html#2DPicker">App Structure</a> guide.</p>
+
+
+<h2 id="Voice" style="clear:both">Voice commands</h2>
+
+  <img src="{@docRoot}design/media/wear/voice_commands.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>It is possible for apps to take action in response to Android voice commands that invoke intents. For example, an app can register for the “Take a note” intent and capture the subsequent voice input for processing. In the case where multiple apps registered for the same intent, user preference will be captured once and saved. Users can edit their intent preferences in the Android Wear app on their handheld.</p>
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/apps/voice.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>Adding Voice Capabilities</p>
+  </div>
+</a>
+
+
+<h2 id="Selection" style="clear:both">Selection List</h2>
+
+  <img src="{@docRoot}design/media/wear/selection_list.png" width="147" height="147" style="float:right;margin:0 0 20px 40px;border:1px solid #ddd">
+
+<p>Choosing an item from a list is a common interaction. The Selection List pattern (available as the <a
+href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>WearableListView</code></a> component) creates a simple list optimized for ease of use on a small screen: the focused item snaps to the center of the screen, and a single tap selects. This widget is recommended as a common pattern for selecting items. It is used throughout the system UI, including in the list that can be accessed by swiping up on the cue card.</p>
+
+
+<p>Of course, it is possible for Android Wear apps to extend themselves beyond the familiarities of these patterns. For a deeper look at the options available, see the <a href="{@docRoot}design/wear/structure.html">App Structure</a> guide.</p>
diff --git a/docs/html/design/wear/principles.jd b/docs/html/design/wear/principles.jd
new file mode 100644
index 0000000..6286a92
--- /dev/null
+++ b/docs/html/design/wear/principles.jd
@@ -0,0 +1,75 @@
+page.title=Design Principles for Android Wear
+@jd:body
+
+<style>
+p.try {
+  color:#888;
+}
+p.try b {
+font-size: 22px;
+font-weight: 200;
+padding: 0 0 5px;
+display: block;
+}
+h2 {
+  margin-top:50px;
+}
+</style>
+
+<p>These design principles provide some simple heuristics about how you should plan and assess your
+Android Wear app design.</p>
+
+
+<h2>Focus on not stopping the user and all else will follow</h2>
+
+<img src="{@docRoot}design/media/wear/five_seconds.gif" height="147" style="float:right;margin:10px 0 0 40px" />
+<p class="img-caption" style="width:315px;float:right;clear:right;margin:5px 0 30px 40px">
+The time required for each action on the left is 5 seconds.</p>
+
+<p>A watch is a perfect form factor for a device that you can use while doing something else, such as cooking, eating, walking, running, or even having a conversation. If using your wearable app causes the user to stop whatever they’re doing, it’s a good occasion to consider how to improve it using the principles in this section.</p>
+
+<p class="try" ><b>Try this:</b> Time a typical use of your Wear app. If using it takes more than 5 seconds, you should think about making your app more focused. Also try using your app while you’re having a conversation, and see how it affects your train of thought and eye contact.</p>
+
+
+<h2 id="BigGestures">Design for big gestures</h2>
+
+<img src="{@docRoot}design/media/wear/biggesture.png" alt="" height="147" style="float:right;margin:10px 0 0 40px" />
+<p class="img-caption" style="width:315px;float:right;clear:right;margin:5px 0 30px 40px">
+Use few and large touch targets.</p>
+
+<p>When you swipe through photos on your phone you’re using a large area of the display, and you don’t have to be precise at all. That’s the best kind of interaction for a wearable device. Your users are going to use your app in all sorts of situations, the least frequent one might actually be sitting down at their desk.</p>
+
+<p class="try"><b>Try this:</b> Use your app in various everyday situations, such as walking, eating, talking to people, or ordering coffee. If you have to slow down while walking or stop the conversation to be precise, you should consider how your gestures could be bigger.</p>
+
+
+<h2 id="CardsFirst">Think about stream cards first</h2>
+
+<p class="img-caption" style="width:150px;float:right;clear:right;margin:10px 0 30px 25px">An app that offers to check in users could appear in the stream suggesting the most likely place nearby, after a certain amount of time.</p>
+
+<img src="{@docRoot}design/media/wear/wear_checkin.png" style="float:right;margin:10px 0 40px 40px" width="147" height="147">
+
+<p>The best experience on a wearable device is when the right content is there just when the user needs it. You can figure out when to show your cards with sensors, or events happening in the cloud. For the cases where it’s impossible to know when the user needs your app, you can rely on a voice action or touch.</p>
+
+<p class="try"><b>Try this:</b> Make a list of all the situations a user would find your app useful. What do they have in common? Same location? Time of day? Certain physical activities? You will most likely come up with several different situations - that’s a good sign, because it means that you can specialize your cards to those situations. Remember that the user always has the option of completely muting your stream cards if they feel they aren’t relevant enough.</p>
+
+
+
+<h2 id="Fast">Do one thing, really fast</h2>
+
+<p>While users will engage with your app for only a few seconds at time, they'll use it many times throughout the day. A well-designed stream card carries one bit of information and potentially offers a few action buttons when the user swipes over.</p>
+
+<p class="try"><b>Try this:</b> How many bits of information is there in your design? Is everything absolutely necessary, or could you split it up into separate cards? If you’re designing a card, don’t forget that you can use multiple pages.</p>
+
+
+<h2 id="CornerOfEye">Design for the corner of the eye</h2>
+
+<p>The longer the user is looking at your app, the more you are pulling them out of the real world. Thinking about how to design your app for glanceability can vastly help the user get full value from your app and quickly go back to what they were doing.</p>
+
+<p class="try"><b>Try this:</b> To view your app with your peripheral vision, try focusing on your knuckles while your watch is displaying the app. Do you get a sense of what it is trying to do? Is it distinguishable from other apps? Does the background image help conveying the message? Does it use photos or a distinct shape and color?</p>
+
+
+<h2 id="Tapper">Don’t be a constant shoulder tapper</h2>
+
+<p>A watch constantly touches the user’s skin. Being this intimate, you want to buzz the watch fewer times than you’re used to on the phone.</p>
+
+<p class="try"><b>Try this:</b> Next time you’re in a conversation, imagine someone tapping you your shoulder, interrupting you with the information you want your app to deliver. If the information delivered did not justify suspending a conversation, you should not make the notification interruptive.</p>
\ No newline at end of file
diff --git a/docs/html/design/wear/structure.jd b/docs/html/design/wear/structure.jd
new file mode 100644
index 0000000..67b218a
--- /dev/null
+++ b/docs/html/design/wear/structure.jd
@@ -0,0 +1,129 @@
+page.title=App Structure for Android Wear
+@jd:body
+
+
+<p>Users are used to tapping icons to launch apps. Android Wear is different. A typical Wear app adds a card to the stream at a contextually relevant moment. It might have a button that opens a full screen view for a fast micro interaction, like below, but it just as well might not.
+</p>
+
+<img src="{@docRoot}design/media/wear/appstructuresample.png" alt="" />
+
+<p>These are the building blocks, ordered by simplicity. You can use one of them or some of them, but we strongly recommend not building apps the user has to launch and quit before thinking really hard about how you could react to a specific location, activity, time of day, or something happening in the cloud.</p>
+
+
+<ul>
+  <li><strong>Contextual card in the stream</strong>:
+  <ul>
+    <li><a href="#Bridged"><strong>Bridged notifications</strong></a> are pushed to the wearable from the connected handheld such as new message notifications, using standard Android notifications. These require little or no code on the wearable.
+    <li><a href="#Contextual"><strong>Contextual notifications</strong></a> are generated locally on the wearable and appear at contextually relevant moments such as an exercise card that appears when you’re going on a run. You can do more with this kind of card than with a notification bridged from the handheld.
+  </ul>
+  </li>
+  <li><strong>Full screen UI app</strong>:
+  <ul>
+    <li><a href="#2DPicker"><strong>2D Picker</strong></a> is design pattern that allows the user to select from a set of items, such as choosing artists and then albums. We recommend using this pre-built component from the SDK whenever relevant.
+    <li><a href="#Custom"><strong>Custom layouts</strong></a> are also possible where apps need to extend beyond the basic card/stream metaphor.</li>
+  </ul>
+  </li>
+</ul>
+
+<p>Many applications will consist of a combination of these views, possibly with connections between them. For example, a contextual card may have an action that launches a more immersive experience. Inversely, an immersive experience may result in a card being added to the stream.</p>
+
+
+<h2 id="Bridged">Bridged Notifications</h2>
+
+<img src="{@docRoot}design/media/wear/bridgednotifications.jpg" height="147" style="float:right;margin:0 0 20px 20px" alt="">
+<img src="{@docRoot}design/media/wear/Bluebird.png" height="147" style="float:right;margin:0 0 20px 40px" alt="">
+
+<p>This is the simplest way to get on Android Wear. In fact, your app already does this if it uses notifications. You can add Wear-specific features like extra pages and voice replies by using the new notification APIs. </p>
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/notifications/creating.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>Creating a Notification</p>
+  </div>
+</a>
+
+
+<h2 id="Contextual" style="clear:both">Contextual Notifications</h2>
+
+
+<img src="{@docRoot}design/media/wear/context_workout.png" width="323" height="147" style="float:right;margin:0 0 20px 40px;clear:right" alt="" />
+<img src="{@docRoot}design/media/wear/context_lights.png" width="323" height="147" style="float:right;margin:0 0 20px 40px;clear:right" alt="" />
+
+<p>This is what Android Wear does best; showing users information just when they need it.</p>
+
+<p>Here’s how it works: Your app knows when it is relevant for the user, and when it happens, you trigger a contextual notification. Maybe you’re building a running app that’s relevant when the user is running. Maybe it’s a museum guide that’s relevant when the user is visiting a particular museum. Check out the design principles for more on thinking about your app contextually. </p>
+
+<p>Getting contextual triggering right is one of the most impactful things you can do to craft a great user experience. </p>
+
+<p>The easiest way to do this is to use standard templates for Android notifications. If you decide to make your own ActivityView, we strongly recommend you take a look at the <a href="{@docRoot}design/wear/style.html">Style</a> guide to make sure you stay consistent with the rest of the device. </p>
+
+<p>Don’t forget to test your triggering thoroughly. Triggering too often can be so annoying that users might end up blocking all your notifications. </p>
+
+
+
+<h2 id="2DPicker">2D Picker</h2>
+
+<p>The 2D Picker design pattern (available as the <a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary">GridViewPager component</a>) is useful for showing a range of options or asking a user to make a quick selection. Google search results on Android Wear are a great example of the GridViewPager pattern in action.</p>
+
+<img src="{@docRoot}design/media/wear/1D_picker.png" alt="" width="499px" />
+<p class="img-caption">This pattern can be used to present a single vertical list, or a “1D Picker”</p>
+
+<img src="{@docRoot}design/media/wear/2D_picker.png" alt="" width:760px" />
+<p class="img-caption">It can also be used as a 2D matrix of options, as a way of presenting categorized options.</p>
+
+
+<img src="{@docRoot}design/media/wear/2d_picker_indicated.png" alt="" width="760px" />
+<p class="img-caption">Navigation should be vertical-then-horizontal, not horizontal-then-vertical, and limit the the vertical set to around five cards.</p>
+
+
+<h3>Actions</h3>
+<p>For actions on each cards, use the <a href="{@docRoot}design/wear/patterns.html#Actions">Action cards pattern</a>.</p>
+
+
+<h3>Exiting</h3>
+<p>2D Picker should be automatically dismissed when a selection is made. It may also be exited by swiping back down on the first card, or by swiping left to right on a leftmost card.</p>
+
+<h3>Making it fast</h3>
+<p>A few of our favorite tips on how to make the 2D picker really fast for your users:</p>
+<ul>
+<li>Minimize the number of cards</li>
+<li>Show the most popular card at the top </li>
+<li>Keep the cards extremely simple</li>
+<li>Optimize for speed over customization</li>
+
+</ul>
+
+
+
+<h2 id="Custom">Breaking out of the card (with custom layouts)</h2>
+
+<a class="notice-developers" href="{@docRoot}training/wearables/apps/index.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>Creating Wearable Apps</p>
+  </div>
+</a>
+
+<p>There are some things you can’t do on a card. Swiping in many directions on a map or a joystick are a few examples. In those cases it might be good idea to momentarily go full screen.</p>
+
+<img src="{@docRoot}design/media/wear/customlayout.png" alt="" width="760px" />
+
+<h3>When to go full screen</h3>
+<p>We highly recommend only going full screen when you can’t do what you want on a card, and quickly exit back to the stream the moment you’re done. That way your app will feel like an integrated part of the system.</p>
+
+<h3>Be distinct</h3>
+<p>Your full screen design shouldn’t look too much like the card stream or it could confuse users. The 2D picker is always available if you need a card-like UI.</p>
+
+<h3>Automatically exiting</h3>
+<p>Many devices don’t have back or home buttons, so exiting is your responsibility as the app designer. Here’s a few examples of natural exits:</p>
+<ul>
+  <li>A map that asks the user to drop a pin should exit when it happens</li>
+  <li>A short game can exit when the game finishes</li>
+  <li>A drawing app can finish after 5 seconds of inactivity.</li>
+</ul>
+
+<h3>Manually exiting</h3>
+<p>Even with logical exit points like these, some cases may exist where the user may want to immediately exit. This may be common in apps that are used for a longer while. In all cases, the developer should present the option to quit the app on long press using 
+<a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>DismissOverlayView</code></a>. Your design should reserve long press for the sole purpose of prompting to quit.</p>
+
+
diff --git a/docs/html/design/wear/style.jd b/docs/html/design/wear/style.jd
new file mode 100644
index 0000000..948b934
--- /dev/null
+++ b/docs/html/design/wear/style.jd
@@ -0,0 +1,115 @@
+page.title=Style for Android Wear
+@jd:body
+
+
+<p>Here are a number of design considerations to bear in mind that are particular to Android Wear.</p>
+
+
+
+<img src="{@docRoot}design/media/wear/circle_message2.png" height="200"
+   style="float:right;margin:32px 0 50px 40px;">
+
+<img src="{@docRoot}design/media/wear/fitness.png" height="200"
+  style="float:right;margin:32px 0 50px 40px;">
+
+<h2 id="ScreenSize">Screen Size</h2>
+
+<p>Be mindful of different device sizes and shapes. Wearable devices are a form of fashion and expression for their owners, and so Android Wear supports  a variety of forms. Most of the complexities of supporting these different devices is taken care of at a system level, but bear in mind different screen types when designing custom full screen apps.</p>
+
+<p>Use the Android Wear emulator to test both square and round devices, and note that <a
+href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>WatchViewStub</code></a> is available to activities to detect whether a square or round device is being used.</p>
+
+
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-6">
+
+  <img src="{@docRoot}design/media/wear/assets_specifics.png" width="300"
+    style="margin:32px 0 20px;">
+  </div>
+  <div class="layout-content-col span-7">
+  <h2 id="Assets" style="clear:both">Specific Assets Required</h2>
+
+  <p>A core set of standard assets may need to be provided depending on your card design: app icon, background image or images, action icons, actions confirmation animation. Of course, your specific design may necessitate other assets. Background image should be provided in landscape format at least 600px width for notifications that include pages of cards, since the system automatically adds a parallaxing effect.</p>
+  </div>
+</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-6">
+  <h2 id="PeekCard" style="clear:both">Peek Card Readability</h2>
+
+  <p>Test your card layout to ensure that useful information is conveyed in the peek state on the Home screen. The main message of the card should be readable in the peek state, particularly for contextual cards. Content that requires an interaction to be read, for example a long message, should be cropped appropriately to provide an affordance to the user to swipe the card to read more.</p>
+  </div>
+  <div class="layout-content-col span-6">
+
+  <img src="{@docRoot}design/media/wear/peek-card.png" width="300"
+    style="margin:12px 0 0 20px">
+  </div>
+</div>
+
+
+
+  <img src="{@docRoot}design/media/wear/low_info_card.png" width="147" height="147"
+  style="float:right;margin:29px 0 20px 40px">
+
+<h2 id="InfoDensity" style="margin-top:0" >Low Information Density</h2>
+
+<p>Cards should be designed to be glanceable in a split second, just like reading the time on a traditional watch. In most cases a pairing of an icon and value, or a title and short caption should be enough to convey a meaningful message. Note that the background photo should also be used to convey information; backgrounds that change to reflect and support the primary message in the card work great. For example, in the case illustrated above a suitable background image is chosen to reflect severity of the current traffic conditions. This is not just a nice piece of attention to detail; the background actually reinforces the message and makes the content more glanceable.</p>
+
+
+<img src="{@docRoot}design/media/wear/separate_info_cards.jpg" height="147"
+  style="float:right;margin:29px 0 20px 40px">
+
+<h2 id="Chunks">Separate Information into Chunks</h2>
+
+<p>In cases where additional information is absolutely necessary, don’t crowd out a card layout to the point where glanceability is affected. Instead, add an additional <a href="{@docRoot}design/wear/patterns.html#Pages">page</a> (or multiple pages, if needed) to the right of the main card in the stream to which the user can swipe for more information. See also <a
+href="{@docRoot}design/wear/patterns.html#Continuing">Continuing activities on phone</a>.</p>
+
+
+<h2 id="KeepMinimum" style="clear:both">Keep Notifications to a Minimum</h2>
+
+<p>Don’t abuse the user’s attention. Active notifications (that is, those that cause the device to vibrate) should only be used in cases that are both timely and involve a contact, for example receiving a message from a friend. Non-urgent notifications should be silently added to the Context Stream. See also the general Android Notifications Guidelines.</p>
+
+
+
+
+<img src="{@docRoot}design/media/wear/clear_bold_type.jpg" height="147"
+  style="float:left;margin:19px 40px 20px 0">
+
+<h2 id="Typography" >Use Clear, Bold Typography</h2>
+
+<p>The system font is Roboto Condensed, with Regular and Light variants. Text should adhere to the size and color recommendations (see the UI Toolkit in the <a href="{@docRoot}design/downloads/index.html#Wear">Downloads</a> page). In general, text should be displayed as large as possible. Your goal should be to convey maximum information with minimum fuss.</p>
+
+
+<h2 id="Branding" style="clear:both" >Use Consistent Branding and Color</h2>
+
+<p>The app icon is used to identify and brand your application. The icon is optional but when present always appears in the same location, overhanging the top edge of the card at the right. Note that app icons or branding should not be displayed in the background photo, which is reserved to display an image relevant to the information on the card.</p>
+
+
+<img src="{@docRoot}design/media/wear/copywrite.png" height="147"
+  style="float:left;margin:19px 40px 20px 0">
+
+<h2 id="Copywrite" >Copywrite Sparingly</h2>
+
+<p>Omit needless text. Design for glanceability, not reading. Use words and phrases, not sentences. Use icons paired with values instead of text wherever possible. Text strings should be as concise as possible, and long pieces of text will be truncated to fit on a single card.</p>
+
+
+<h2 id="BeDiscreet" style="clear:both" >Be Discreet if Necessary</h2>
+
+<p>Wearables are personal devices by nature, but they are not completely private. If your notification serves content that may be particularly sensitive or embarrassing (such as notifications from a dating app or a medical status report), consider not displaying all of the information in a peek card. A notification could place the sensitive information on a second page that must be swiped to, or an application could show different amounts of detail in peek and focused card positions.</p>
+
+
+  <img src="{@docRoot}design/media/wear/confirmation.png" height="147"
+  style="float:left;margin:29px 40px 20px 0">
+
+
+<h2 id="ConfirmAnim">Confirmation Animations</h2>
+
+<p>If your app allows the user to perform an action, it is necessary to provide positive feedback. Show a generic confirmation animation or create your own. A confirmation animation is an opportunity to express your app’s character and insert a moment of delight for your user. Keep animations short (less than 1000ms) and simple. Animating the confirmation icon is an effective way of transitions the user to a new state after completing an action.</p>
+
+
+
+
+
+
+
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/essentials_toc.cs b/docs/html/distribute/essentials/essentials_toc.cs
index 7084fdd..d0a1114 100644
--- a/docs/html/distribute/essentials/essentials_toc.cs
+++ b/docs/html/distribute/essentials/essentials_toc.cs
@@ -11,12 +11,6 @@
     </div>
   </li>
   <li class="nav-section">
-    <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/essentials/gpfe-guidelines.html">
-          <span class="en">Education Guidelines</span>
-        </a>
-    </div>
-  </li>
-  <li class="nav-section">
     <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/essentials/optimizing-your-app.html">
           <span class="en">Optimize Your App</span>
         </a>
diff --git a/docs/html/distribute/essentials/gpfe-guidelines.jd b/docs/html/distribute/essentials/gpfe-guidelines.jd
deleted file mode 100644
index 799009f..0000000
--- a/docs/html/distribute/essentials/gpfe-guidelines.jd
+++ /dev/null
@@ -1,509 +0,0 @@
-page.title=Education Guidelines
-page.metaDescription=These guidelines and requirements help you develop great apps for students, which offer compelling content and an intuitive user experience on Android tablets.
-page.image=/distribute/images/edu-guidelines.jpg
-Xnonavpage=true
-
-@jd:body
-
-
-<div id="qv-wrapper"><div id="qv">
-<h2>Guidelines</h2>
-<ol>
-<li><a href="#basic-reqts">Basic Requirements</a></li>
-<li><a href="#monetizing-ads">Monetizing and Ads</a></li>
-<li><a href="#e-value">Educational Value</a></li>
-<li><a href="#quality">App Quality</a></li>
-<li><a href="#related-resources">Related Resources</a></li>
-</ol>
-
-<h2>
-  Testing
-</h2>
-
-<ol>
-  <li>
-    <a href="#test-environment">Setting Up a Test Environment</a>
-  </li>
-</ol>
-
-</div></div>
-
-<div style="margin:0 0 1em 0;">
-  <img src="{@docRoot}distribute/images/edu-guidelines.jpg" style=
-  "width:274px;">
-</div>
-
-<p>
-  These guidelines and requirements help you develop great apps for students,
-  which offer compelling content and an intuitive user experience on Android
-  tablets.
-</p>
-
-<p>
-  You’ll also need to ensure that your apps comply with the terms of the
-  <a href=
-  "https://play.google.com/about/developer-distribution-agreement-addendum.html">
-  Google Play for Education Addendum</a>, <a href=
-  "http://play.google.com/about/developer-content-policy.html">Google Play
-  Developer Program Policies</a>, and <a href=
-  "http://play.google.com/about/developer-distribution-agreement.html">Developer
-  Distribution Agreement</a>.
-</p>
-
-<div class="headerLine clearfloat">
-  <h1 id="basic-reqts">
-    Basic Requirements
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  To participate, your apps must be designed for the K-12 market. The basic
-  requirements that your apps must meet are:
-</p>
-
-<ul>
-  <li>
-    <p>
-      Apps and the ads they contain must not collect personally identifiable
-      information, other than user credentials or data required to operate and
-      improve the app.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Apps must not use student data for purposes unrelated to its educational
-      function.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Apps must have a content rating of "Everyone" or "Low Maturity" (apps
-      with a "Medium Maturity" rating are allowed, if they have that rating
-      solely because they allow communication between students).
-    </p>
-  </li>
-
-  <li>
-    <p>
-      App content, including ads displayed by the app, must be consistent with
-      the app's maturity rating. The app must not display any "offensive"
-      content, as described in the <a href=
-      "http://play.google.com/about/developer-content-policy.html">Google Play
-      Developer Program Policies</a> and <a href=
-      "https://support.google.com/googleplay/android-developer/answer/188189">content-rating
-      guidelines</a>.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Apps must comply with the Children’s Online Privacy Protection Act and
-      all other applicable laws and regulations.
-    </p>
-  </li>
-</ul>
-
-<div class="headerLine">
-  <h1 id="monetizing-ads">
-    Monetizing and Ads
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  In-app purchase is currently not supported with Google Play for Education, so
-  a student device will block any transactions. To avoid confusion, be sure to
-  remove any in-app purchase buttons and related UI elements from your apps.
-  We’re investigating additional purchase mechanisms to enable more flexible
-  pricing models for developers and schools.
-</p>
-
-<p>
-  If your apps are priced In Google Play for Education, you must allow Google
-  Play to offer teachers limited free trials before purchase (you provide this
-  through business terms only, no development work is needed.)
-</p>
-
-<p>
-  You can only choose not to remove in-app purchasing from your apps where all
-  content and services are sold through Google Play for Education using In-app
-  Billing. If you choose not to remove In-app Billing features, ensure that:
-</p>
-
-<ul>
-  <li>
-    <p>
-      Users can access your apps’ core functionality for a classroom setting
-      without an in-app purchase.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      In-app purchases are clearly identifiable in your UI.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      You declare the use of in-app purchases at <a href=
-      "{@docRoot}distribute/googleplay/edu/start.html#publish">opt-in</a>.
-    </p>
-  </li>
-</ul>
-
-<p>
-  For each app that you publish, you can set a single price that applies to
-  both Google Play and Google Play for Education. You can’t set a different
-  price for a given app (based on a single package name) in Google Play for
-  Education.
-</p>
-
-<p>
-  If your apps display ads, you should disable the display of ads if possible,
-  or ensure that:
-</p>
-
-<ul>
-  <li>Ads are not distracting for students or teachers (this includes
-  Flash-based ads, video ads, and ads that flash or move)
-  </li>
-
-  <li>Interstitial ads are not served in the app
-  </li>
-
-  <li>Ad walls do not appear in the app UI
-  </li>
-
-  <li>Ads do not occupy a significant portion of the screen
-  </li>
-
-  <li>Ads content does not exceed the maturity rating of the app.
-  </li>
-
-  <li>
-    <p>
-      You declare the use of ads at <a href=
-      "{@docRoot}distribute/googleplay/edu/start.html#publish">opt-in</a>.
-    </p>
-  </li>
-</ul>
-
-<div class="headerLine">
-  <h1 id="e-value">
-    Educational Value
-  </h1>
-
-  <hr>
-</div>
-
-<div class="figure">
-  <img src="{@docRoot}images/gp-e-value.png" class="border-img">
-</div>
-
-<p>
-  Apps submitted to Google Play for Education will be evaluated by a
-  third-party educator network, which will review them based on alignment with
-  <a href="http://www.corestandards.org/">Common Core Standards</a> and other
-  educational considerations. This will help make your content more
-  discoverable for teachers and administrators as they browse by grade level,
-  subject, core curriculum, and other parameters.
-</p>
-
-<p>
-  Apps with highest educational value will have these characteristics:
-</p>
-
-<ul>
-  <li>
-    <p>
-      Designed for use in K-12 classrooms.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Aligned with a common core standard or support common-core learning.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Simple, easy to use, and intuitive for the grade levels the apps are
-      targeting. Apps are relatively easy to navigate without teacher guidance.
-      Not distracting or overwhelming to students.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Enjoyable and interactive. Apps are engaging to students and lets them
-      control their experience.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Versatile. Apps have features that make them useful for more than one
-      classroom function or lesson throughout the school year.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      Supports the "4Cs":
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          <em>Creativity</em> &mdash; Allows students to create in order to
-          express understanding of the learning objectives, and try new
-          approaches, innovation, and invention to get things done.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          <em>Critical thinking</em> &mdash; Allows students to look at
-          problems in a new way, linking learning across subjects and
-          disciplines.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          <em>Collaboration</em> &mdash; Allows students and (if appropriate)
-          educators to work together to reach a goal.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          <em>Communication</em> &mdash; Allows students to comprehend,
-          critique and share thoughts, questions, ideas, and solutions.
-        </p>
-      </li>
-    </ul>
-  </li>
-</ul>
-
-<p>
-  As you design and develop your apps, make sure they offer high educational
-  value by addressing as many of these characteristics as possible.
-</p>
-
-<div class="headerLine">
-  <h1 id="quality">
-    App Quality
-  </h1>
-
-  <hr>
-</div>
-
-<div class="figure">
-  <img src="{@docRoot}images/gp-edu-quality.png">
-</div>
-
-<p>
-  Your apps should be designed to perform well and look great on Android
-  tablets, and they should offer the best user experience possible.
-</p>
-
-<p>
-  High quality apps are engaging, intuitive, and offer compelling content.
-  Google Play for Education will highlight high-quality apps for easy discovery
-  in the store. Here are some recommendations for making your app easy for
-  students and teachers to enjoy:
-</p>
-
-<ul>
-  <li>
-    <p>
-      Meet the Core Quality Guidelines:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          Follow <a href="{@docRoot}design/index.html">Android Design
-          Guidelines</a>. Pay special attention to the sections on <a href=
-          "{@docRoot}design/patterns/actionbar.html">Action Bar</a>, <a href=
-          "{@docRoot}design/patterns/navigation.html">Navigation</a>, and
-          <a href="{@docRoot}design/patterns/pure-android.html">Pure
-          Android</a>.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Test your apps against the <a href=
-          "{@docRoot}distribute/essentials/quality/core.html">Core Quality
-          Guidelines</a>.
-        </p>
-      </li>
-    </ul>
-  </li>
-
-  <li>
-    <p>
-      Meet the Tablet App Quality guidelines:
-    </p>
-
-    <ul>
-      <li>
-        <p>
-          Follow our best practices for tablet app development.
-        </p>
-      </li>
-
-      <li>
-        <p>
-          Review the <a href=
-          "{@docRoot}distribute/essentials/quality/tablets.html">Tablet App
-          Quality</a> guidelines and <a href=
-          "http://android-developers.blogspot.com/2012/11/designing-for-tablets-were-here-to-help.html">
-          blog post on designing for tablets.</a>
-        </p>
-
-        <ul>
-          <li>Check your Optimization Tips in the <a href=
-          "https://play.google.com/apps/publish/">Developer Console</a> (if
-          you've already uploaded your apps.)
-          </li>
-        </ul>
-      </li>
-
-      <li>
-        <p>
-          Strive for simplicity and highest usability for students:
-        </p>
-
-        <ul>
-          <li>
-            <p>
-              Design your app so that teachers and students can use all the
-              capabilities of your app without having to sign-in to multiple
-              accounts and remember multiple passwords.
-            </p>
-          </li>
-
-          <li>
-            <p>
-              Every student or teacher using a Google Play for Education tablet
-              will already be signed in with a Google account on the device.
-              You can take advantage of that to provide a simple, seamless
-              sign-in experience in your app. A recommended approach is to use
-              <a href="{@docRoot}google/play-services/auth.html">Google OAuth 2
-              authorization</a> through Google Play Services.
-            </p>
-          </li>
-        </ul>
-      </li>
-    </ul>
-  </li>
-</ul>
-
-<div class="headerLine">
-  <h1 id="test-environment">
-    Test Environment
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  To test your app and assess it against the guidelines in this document, it's
-  recommended that you <a href=
-  "{@docRoot}distribute/essentials/quality/tablets.html#test-environment">set
-  up a test environment</a> that replicates the actual environment in which
-  students and teachers will run your app.
-</p>
-
-<h3>
-  Test conditions
-</h3>
-
-<p>
-  Make sure to test your apps under conditions that simulate those of schools.
-  For example, Google Play for Education lets administrators <a href=
-  "https://support.google.com/a/answer/182442?hl=en">control or disable certain
-  capabilities</a> for students, so it's good to test your app with those
-  capabilities disabled. Below are some conditions to test your apps for, to
-  ensure best results in the Google Play for Education environment:
-</p>
-
-<ul>
-  <li>
-    <p>
-      <em>Android version</em> &mdash; Test the apps on devices running Android
-      4.2. Google Play for Education devices will be running Android 4.2 or
-      higher (API level 17+).
-    </p>
-  </li>
-
-  <li>
-    <p>
-      <em>Proxy server</em> &mdash; Test the apps in a network environment that
-      uses proxies. Many schools use proxies.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      <em>No location services</em> &mdash; Test the apps to make sure they
-      work properly with location services disabled. Many schools will disable
-      location services for student devices.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      <em>No In-app Billing</em> &mdash; Test the apps to make sure they work
-      properly without access to In-app Billing. In-app purchases are blocked
-      on Google Play for Education devices.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      <em>No Bluetooth</em> &mdash; Test the apps to make sure they work
-      properly when Bluetooth is disabled. Many schools will disable Bluetooth
-      on student devices.
-    </p>
-  </li>
-
-  <li>
-    <p>
-      <em>No access to network</em> &mdash; Test the app to make sure it works
-      properly when the device cannot connect to the internet.
-    </p>
-  </li>
-</ul>
-
-<div class="headerLine">
-<h1>Related Resources</h1><hr>
-</div>
-
-<div class="dynamic-grid">
-<h3>FOR DEVELOPERS</h3>
-
-<div class="resource-widget resource-flow-layout col-13"
-    data-query="collection:distribute/essentials/eduessentials/developers"
-    data-sortOrder="-timestamp"
-    data-cardSizes="6x3,6x3,6x3"
-    data-maxResults="6"></div>
-
-<h3>FOR TEACHERS AND EDUCATORS</h3>
-
-<div class="resource-widget resource-flow-layout col-13"
-    data-query="collection:distribute/essentials/eduessentials/educators"
-    data-sortOrder="-timestamp"
-    data-cardSizes="6x3,6x3,6x3"
-    data-maxResults="3"></div>
-</div>
\ No newline at end of file
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..60d9402 100644
--- a/docs/html/distribute/googleplay/edu/about.jd
+++ b/docs/html/distribute/googleplay/edu/about.jd
@@ -9,7 +9,7 @@
 
 <p>
   Google Play for Education is an extension of Google Play designed for
-  schools. Here educators can discover apps approved by teachers for teachers,
+  schools. Here educators can discover Android and Chrome apps approved by teachers for teachers,
   as well as educational videos and a collection of classic books for their
   classroom.
 </p>
@@ -22,7 +22,7 @@
 </p>
 
 
-<div class="resource-widget resource-flow-layout col-13"
+<div class="resource-widget resource-flow-layout col-13" style="height:323px"
   data-query="collection:distribute/googleplay/gpfe/highlight"
   data-sortOrder="-timestamp"
   data-cardSizes="18x6,"
@@ -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
deleted file mode 100644
index 0866da5..0000000
--- a/docs/html/distribute/googleplay/edu/faq.jd
+++ /dev/null
@@ -1,433 +0,0 @@
-page.title=Education FAQ
-meta.tags="gpfe, edu"
-page.metaDescription=Answers to frequent questions about Google Play for Education.
-page.image=/distribute/images/gpfe-faq.jpg
-
-@jd:body
-
-<div id="qv-wrapper">
-  <div id="qv">
-  <h2>
-    Topics
-  </h2>
-
-  <ol>
-    <li>
-    <a href="#business-model-and-monetization">Business Model and
-    Monetization</a>
-    </li>
-
-    <li>
-    <a href="#free-trials">Free Trials</a>
-    </li>
-
-    <li>
-    <a href="#discovery">Discovery</a>
-    </li>
-
-    <li>
-    <a href="#app-review-process">App Review Process</a>
-    </li>
-
-    <li>
-    <a href="#app-features">App Features</a>
-    </li>
-
-    <li>
-    <a href="#marketing-and-roi">Marketing and ROI</a>
-    </li>
-
-    <li>
-    <a href="#devices">Devices</a>
-    </li>
-
-    <li>
-    <a href="#accounts">Accounts</a>
-    </li>
-
-    <li>
-    <a href="#related-resources">Related Resources</a>
-    </li>
-  </ol>
-  </div>
-</div>
-
-<p>
-  This page provides answers to common questions that you might have about
-  Google Play for Education.
-</p>
-
-<div class="headerLine">
-  <h1 id="business-model-and-monetization">
-  Business Model and Monetization
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>What is Google Play for Education?</strong>
-</p>
-
-<p>
-  Google Play for Education is a new online destination designed for schools.
-  Teachers can discover educational apps, books, and videos to meet the needs
-  of a single student, a classroom, or a whole district. Educators can browse
-  apps by grade, subject, keyword, or standard including Common Core State
-  Standards. Purchasing is done using a PO with no credit card required. Apps
-  are distributed to tablets instantly through the cloud.
-</p>
-
-<p>
-  <strong>Is Google Play for Education primarily for students or
-  educators?</strong>
-</p>
-
-<p>
-  The store on Google Play for Education is for educators, but its content is
-  for both educators and students. Teachers and administrators have the ability
-  to make purchases and control who within their school has access to the
-  purchase flows.
-</p>
-
-<div class="figure">
-  <img src="{@docRoot}distribute/images/gpfe-faq.jpg" style=
-  "width:480px;margin:1em 0em 1.5em 1.5em;">
-</div>
-
-<p>
-  <strong>Will Google Play for Education support subscription
-  purchases?</strong>
-</p>
-
-<p>
-  Currently, Google Play for Education supports one-time purchases. We’re
-  investigating additional purchase mechanisms to enable more flexible pricing
-  models for developers and schools.
-</p>
-
-<p>
-  <strong>Why is it recommended that in-app purchase features are
-  removed?</strong>
-</p>
-
-<p>
-  In-app Billing is currently not supported with Google Play for Education, and
-  a student device will block the Google Play transaction if a student attempts
-  to make an in-app purchase. To avoid confusing students, we recommend not
-  including any in-app purchase buttons and other UI in your apps. We’re
-  investigating additional purchase mechanisms to enable more flexible pricing
-  models for developers and schools.
-</p>
-
-<p>
-  <strong>Is Google Play for Education restricted so only its users can
-  purchase from the Google Play for Education? Or will anyone be able to
-  purchase from it?</strong>
-</p>
-
-<p>
-  Currently, only schools that are signed up for Google Play for Education can
-  make purchases on it.
-</p>
-
-<p>
-  <strong>Can I set different prices for my apps in Google Play for Education
-  and Google Play?</strong>
-</p>
-
-<p>
-  You set a single price for each app that applies to both Google Play and
-  Google Play for Education. You can’t set a different price for a given app
-  (based on a single package name) in Google Play for Education.
-</p>
-
-<div class="headerLine">
-  <h1 id="free-trials">
-  Free Trials
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>Can I offer free trials through Google Play for Education?</strong>
-</p>
-
-<p>
-  Google Play for Education doesn't currently support free trials. If you want,
-  you can offer a free version of your app with limited functionality in Google
-  Play for Education, but that app would need to be separate from your paid app
-  and be reviewed separately for educational content.
-</p>
-
-<p>
-  <strong>Can I offer a free trial through Google Play's "In-app Subscriptions
-  with Free Trials" feature?</strong>
-</p>
-
-<p>
-  Google Play for Education doesn’t currently support In-app Billing or In-app
-  Subscriptions with free trials.
-</p>
-
-<div class="headerLine">
-  <h1 id="discovery">
-  Discovery
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>What are the categories in Google Play for Education?</strong>
-</p>
-
-<p>
-  Google Play for Education includes categories for all grade levels from
-  Kindergarten to 12 and the following subjects: English Language Arts, World
-  Languages, Mathematics, Science, Social Science, Elective, Open Education
-  Resources (OER), and Tools.
-</p>
-
-<p>
-  <strong>I created an app specifically for Google Play for Education and don’t
-  want it to show up in Google Play. Is this possible?</strong>
-</p>
-
-<p>
-  Currently, it’s not possible to publish an app on Google Play for Education
-  and make it unavailable on Google Play.
-</p>
-
-<p>
-  <strong>If my app offers content for every level of education, how will it
-  fit the Common Core State Standard filters?</strong>
-</p>
-
-<p>
-  If your app applies to multiple levels of education, then the app will show
-  up in filtered results for multiple levels.
-</p>
-
-<div class="headerLine">
-  <h1 id="app-review-process">
-  App Review Process
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>How are apps being reviewed? By whom and against what
-  criteria?</strong>
-</p>
-
-<p>
-  Apps are being reviewed by a third-party network of educators. These
-  educators assign the appropriate subject, grade, and Common Core State
-  Standards metadata, as well as evaluating whether the app meets the Google
-  Play for Education <a href=
-  "{@docRoot}distribute/essentials/gpfe-guidelines.html">criteria for classroom
-  use</a>.
-</p>
-
-<p>
-  <strong>How do I update my apps in Google Play for Education?</strong>
-</p>
-
-<p>
-  You can update your apps on Google Play for Education in the same manner you
-  do on Google Play. App updates will not be reviewed prior to being made
-  available through Google Play for Education. However, we will periodically
-  review updated apps for quality.
-</p>
-
-<p>
-  <strong>Does the app maturity rating reflect solely on what a user can do
-  within my Android app, or does the web version of my app influence the rating
-  as well?</strong>
-</p>
-
-<p>
-  The maturity rating that you set for an Android app refers only to the
-  content displayed in that app.
-</p>
-
-<div class="headerLine">
-  <h1 id="app-features">
-  App Features
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>Do I need separate builds of my phone and tablet apps for Google Play
-  for Education, or is it the exact same app that lives on Google
-  Play?</strong>
-</p>
-
-<p>
-  We recommend you create one app and use it in both Google Play and Google
-  Play for Education.
-</p>
-
-<p>
-  <strong>What is the best way to get students’ work within apps sent back to
-  their teachers?</strong>
-</p>
-
-<p>
-  Teachers have mentioned that many apps achieve this by email from a third
-  party, which isn’t optimal for schools. As many schools use Google Apps for
-  Education, consider integrating your apps with Google Drive using the
-  <a href="https://developers.google.com/drive/about-sdk">SDK</a>.
-</p>
-
-<p>
-  <strong>How can developers test the teacher experience in Google Play for
-  Education? Is there a way to get an account to test it?</strong>
-</p>
-
-<p>
-  Currently, we are unable to provide developers with a test account to test
-  the Google Play for Education user experience. We’re investigating ways to
-  allow developers to simulate the environment.
-</p>
-
-<p>
-  <strong>If I already have an app in the Chrome Apps Pack will I get some help
-  migrating this to Android?</strong>
-</p>
-
-<p>
-  If you’d like to reach tablet users in schools we encourage you to build a
-  native app for the optimal user experience. Considerations for building your
-  apps can be found in the <a href=
-  "{@docRoot}distribute/essentials/gpfe-guidelines.html">Google Play for
-  Education Guidelines</a>.
-</p>
-
-<div class="headerLine">
-  <h1 id="marketing-and-roi">
-  Marketing and ROI
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>What are you doing to promote these apps to educators?</strong>
-</p>
-
-<p>
-  Google Play for Education is an extension of Google Play targeting schools
-  and making the discovery of educational apps easier. It helps your apps gain
-  visibility with the right audiences, without having to knock on school doors.
-  We’re constantly referring to the highest quality apps in our educator
-  outreach. We’ve also developed a series of collections to help educators
-  quickly browse apps for the most common use cases.
-</p>
-
-<p>
-  <strong>How many installs have similar apps had on Google Play for Education?
-  How much can I expect to make if I do an ROI analysis?</strong>
-</p>
-
-<p>
-  While we cannot disclose specific numbers, Google Play app listings provide
-  app download ranges for all apps.
-</p>
-
-<p>
-  <strong>What is the seasonality like for the education market? What are the
-  key timing considerations for app developers?</strong>
-</p>
-
-<p>
-  In the United States, school districts’ budget decisions go through a
-  planning phase in the Spring with budgets being released on July 1. We’ve
-  observed high purchase-volumes in the second quarter of the calendar year, to
-  use up end-of-year budgets. New budget purchases begin in the third quarter
-  of the calendar year.
-</p>
-
-<p>
-  <strong>Is there a way to offer a special deal, such as a discount, only on
-  Google Play for Education and not on Google Play?</strong>
-</p>
-
-<p>
-  No, this isn’t possible. Pricing, including special offers, must be the same
-  between Google Play for Education and Google Play.
-</p>
-
-<div class="headerLine">
-  <h1 id="devices">
-  Devices
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>Which devices are available in the program? Will more be
-  available?</strong>
-</p>
-
-<p>
-  Nexus 7 is available for shipment now, and the Asus Transformer, HP Slate 8
-  Pro, and Galaxy Tab for Education will be available in early 2014. We look
-  forward to welcoming more Android devices into the Google in Education family
-  soon.
-</p>
-
-<p>
-  <strong>Can the devices be shared among many students?</strong>
-</p>
-
-<p>
-  No. Currently, this program is for one-to-one use. Each student can login to
-  one specific tablet that is allocated to them.
-</p>
-
-<div class="headerLine">
-  <h1 id="accounts">
-  Accounts
-  </h1>
-
-  <hr>
-</div>
-
-<p>
-  <strong>Will an app know whether a user is a teacher or student?</strong>
-</p>
-
-<p>
-  No, the app has no mechanism for knowing if it’s running on a teacher’s
-  device or a student’s device. We recommend developers use their own user
-  database to enable this feature, where logins can be based on Google Account
-  information.
-</p>
-
-<p>
-  <strong>What log-in method do you recommend for an app on Google Play for
-  Education?</strong>
-</p>
-
-<p>
-  One of the key pieces of feedback we’ve heard multiple times from various
-  schools is that they prefer apps that offer Google Single Sign-on, so that
-  teachers and students don’t need to remember multiple log-in credentials. As
-  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="resource-widget resource-flow-layout col-13"
-  data-query="collection:distribute/toolsreference/gpfefaq"
-  data-sortOrder="-timestamp"
-  data-cardSizes="6x3,6x3,6x3,9x3,9x3,9x3"
-  data-maxResults="6"></div>
-
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 260ae85..136611c 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -1,4 +1,4 @@
-page.title=Get Started with Education
+page.title=Publish Android Apps for Education
 page.image=/distribute/images/play-education.jpg
 meta.tags="education", "guidelines", "quality"
 page.tags="education", "addendum"
@@ -16,28 +16,30 @@
 </ol>
 </div></div>
 <p>
-  If you've got great apps for education and want to reach even more teachers
+  If you've got great Android apps for education and want to reach even more teachers
   and students, you can join the <strong>Google Play for Education</strong>
   program in a few simple steps. You do everything using the familiar tools and
   processes in Google Play.
 </p>
 
 <p>
-  Note that Google Play for Education is currently available to <strong>K-12
-  schools in the United States</strong> only.
+Note that Google Play for Education is currently available to <strong>K-12 schools in the United
+States</strong> only.</p>
+
+<p>If you have an educational Chrome app instead of an Android app, you can learn more about
+Chrome Apps in Google Play for Education at <a href=
+"https://developers.google.com/edu">developers.google.com/edu</a>.
 </p>
 
-<div class="center-img">
-  <img src="{@docRoot}images/gpfe-start-0.jpg" style=
-  "border:1px solid #ddd;padding:0px;width:100%;">
-</div>
+<img src="{@docRoot}images/gpfe-start-0.jpg" style=
+  "border:1px solid #ddd;padding:0px" width="760" height="403">
 
-<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 +51,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="prepare">
+  <h2 id="prepare">
     Prepare Your Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure-right">
@@ -138,11 +140,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="publish">
+  <h2 id="publish">
     Publish Your Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -298,7 +300,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/googleplay_toc.cs b/docs/html/distribute/googleplay/googleplay_toc.cs
index 4196c39..36e424a 100644
--- a/docs/html/distribute/googleplay/googleplay_toc.cs
+++ b/docs/html/distribute/googleplay/googleplay_toc.cs
@@ -25,13 +25,7 @@
   </li>
   <li class="nav-section">
     <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/googleplay/edu/start.html">
-          <span class="en">Get Started with Education</span>
-        </a>
-    </div>
-  </li>
-  <li class="nav-section">
-    <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/googleplay/edu/faq.html">
-          <span class="en">Education FAQ</span>
+          <span class="en">Publish Android Apps for Education</span>
         </a>
     </div>
   </li>
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/auth/api-client.jd b/docs/html/google/auth/api-client.jd
index 402a95f..5331d1e 100644
--- a/docs/html/google/auth/api-client.jd
+++ b/docs/html/google/auth/api-client.jd
@@ -1,4 +1,4 @@
-page.title=Accessing Google Play Services APIs
+page.title=Accessing Google APIs
 page.tags="oauth 2.0","GoogleAuthUtil"
 
 trainingnavtop=true
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 03addfd..4389e3d 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -19,7 +19,11 @@
         <li><a href="#response">Response format</a></li>
       </ol>
       </li>
-  <li><a href="#upstream">Upstream Messages</a> </li>
+  <li><a href="#upstream">Upstream Messages</a>
+    <ol>
+      <li><a href="#receipts">Receive return receipts</a></li>
+    </ol>
+  </li>
   <li><a href="#flow">Flow Control</a> </li>
   <li><a href="#implement">Implementing an XMPP-based App Server</a>
     <ol class="toc">
@@ -43,9 +47,6 @@
 </div>
 </div>
 
-<p class="note"><strong>Note:</strong> To try out this feature, sign up using
-<a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
-
 <p>The GCM Cloud Connection Server (CCS) is an XMPP endpoint that provides a
 persistent, asynchronous, bidirectional connection to Google servers. The
 connection can be used to send and receive messages between your server and
@@ -149,8 +150,8 @@
   <li>CCS adds the field {@code message_id}, which is required. This ID uniquely
 identifies the message in an XMPP connection. The ACK or NACK from CCS uses the
 {@code message_id} to identify a message sent from 3rd-party app servers to CCS.
-Therefore, it's important that this {@code message_id} not only be unique, but
-always present.</li>
+Therefore, it's important that this {@code message_id} not only be unique (per
+sender ID), but always present.</li>
 </ul>
 
 <p>In addition to regular GCM messages, control messages are sent, indicated by
@@ -188,7 +189,8 @@
           &quot;hello&quot;:&quot;world&quot;,
       }
       &quot;time_to_live&quot;:&quot;600&quot;,
-      &quot;delay_while_idle&quot;: true/false
+      &quot;delay_while_idle&quot;: true/false,
+      &quot;delivery_receipt_requested&quot;: true/false
   }
   &lt;/gcm&gt;
 &lt;/message&gt;
@@ -227,42 +229,48 @@
 <p>Below are some examples.</p>
 
 <p>Bad registration:</p>
+
 <pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
+  &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
   {
-    &quot;error&quot;:&quot;BAD_REGISTRATION&quot;,  // error code
+    &quot;message_type&quot;:&quot;nack&quot;,
     &quot;message_id&quot;:&quot;msgId1&quot;,
-    &quot;from&quot;:&quot;PA91bHFOtaQGSwupt5l1og&quot;,
-    &quot;message_type&quot;:&quot;nack&quot;
+    &quot;from&quot;:&quot;SomeInvalidRegistrationId&quot;,
+    &quot;error&quot;:&quot;BAD_REGISTRATION&quot;,
+    &quot;error_description&quot;:&quot;Invalid token on 'to' field: SomeInvalidRegistrationId&quot;
   }
-  &lt;/data:gcm&gt;
+  &lt;/gcm&gt;
 &lt;/message&gt;</pre>
 
-<p>Invalid "time to live":</p>
+<p>Invalid JSON:</p>
 
 <pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
-  {
-     &quot;error&quot;:&quot;InvalidJson : INVALID_TTL : Invalid value (-1) for \&quot;time_to_live\&quot;: must be between 0 and \&quot;2419200\&quot;\n&quot;,
-     &quot;message_id&quot;:&quot;msgId1&quot;,
-     &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
-     &quot;message_type&quot;:&quot;nack&quot;
-  }
-  &lt;/data:gcm&gt;
-&lt;/message&gt;</pre>
+ &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
+ {
+   &quot;message_type&quot;:&quot;nack&quot;,
+   &quot;message_id&quot;:&quot;msgId1&quot;,
+   &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
+   &quot;error&quot;:&quot;INVALID_JSON&quot;,
+   &quot;error_description&quot;:&quot;InvalidJson: JSON_TYPE_ERROR : Field \&quot;time_to_live\&quot; must be a JSON java.lang.Number: abc&quot;
+ }
+ &lt;/gcm&gt;
+&lt;/message&gt;
+</pre>
 
-<p>JSON type error:</p>
+<p>Quota exceeded:</p>
 
 <pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
-  {
-     &quot;error&quot;:&quot;InvalidJson : JSON_TYPE_ERROR : Field \&quot;delay_while_idle\&quot; must be a JSON java.lang.Boolean: not-boolean-user-supplied-value\n&quot;,
-     &quot;message_id&quot;:&quot;msgId1&quot;,
-     &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
-     &quot;message_type&quot;:&quot;nack&quot;
-  }
-  &lt;/data:gcm&gt;
-&lt;/message&gt;</pre>
+ &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
+ {
+   &quot;message_type&quot;:&quot;nack&quot;,
+   &quot;message_id&quot;:&quot;msgId1&quot;,
+   &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
+   &quot;error&quot;:&quot;QUOTA_EXCEEDED&quot;,
+   &quot;error_description&quot;:&quot;Short-term downstream quota exceeded for this registration id&quot;
+ }
+ &lt;/gcm&gt;
+&lt;/message&gt;
+</pre>
 
 
 <p>The following table lists NACK error codes. Unless otherwise
@@ -300,7 +308,7 @@
 </tr>
 <tr>
 <td>{@code INVALID_JSON}</td>
-<td>The JSON message payload was not valid.</td>
+<td>The JSON message payload is not valid.</td>
 </tr>
 <tr>
 <td>{@code QUOTA_EXCEEDED}</td>
@@ -309,10 +317,10 @@
 rate.</td>
 </tr>
 <tr>
-<td>{@code SERVICE_UNAVAILABLE}</td>
-<td>CCS is not currently able to process the message. The
-message should be retried over the same connection using exponential backoff
-with an initial delay of 1 second.</td>
+  <td>{@code SERVICE_UNAVAILABLE}</td>
+  <td>CCS is not currently able to process the message. The
+    message should be retried over the same connection using exponential backoff
+    with an initial delay of 1 second.</td>
 </tr>
 </table>
 
@@ -382,8 +390,8 @@
 // Bundle data consists of a key-value pair
 data.putString("hello", "world");
 // "time to live" parameter
-// This is optional. It specifies a value in seconds up to 4 weeks.
-int ttl = [0 seconds, 4 weeks]
+// This is optional. It specifies a value in seconds up to 24 hours.
+int ttl = [0 seconds, 24 hours]
 
 gcm.send(GCM_SENDER_ID + "&#64;gcm.googleapis.com", id, ttl, data);
 </pre>
@@ -419,6 +427,69 @@
   &lt;/gcm&gt;
 &lt;/message&gt;</pre>
 
+<h3 id="receipts">Receive return receipts</h3>
+
+<p>You can use upstream messaging to get receipt notifications, confirming
+that a given message was sent to a device. Your 3rd-party app server receives the receipt
+notification from CCS once the message has been sent to the device.</p>
+
+<p>To enable this feature, the message your 3rd-party app server sends to CCS must include
+a field called <code>&quot;delivery_receipt_requested&quot;</code>. When this field is set to
+<code>true</code>, CCS sends a return receipt. Here is an XMPP stanza containing a JSON
+message with <code>&quot;delivery_receipt_requested&quot;</code> set to <code>true</code>:</p>
+
+<pre>&lt;message id=&quot;&quot;&gt;
+  &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
+  {
+      &quot;to&quot;:&quot;REGISTRATION_ID&quot;,
+      &quot;message_id&quot;:&quot;m-1366082849205&quot;
+      &quot;data&quot;:
+      {
+          &quot;hello&quot;:&quot;world&quot;,
+      }
+      &quot;time_to_live&quot;:&quot;600&quot;,
+      &quot;delay_while_idle&quot;: true,
+      <strong>&quot;delivery_receipt_requested&quot;: true</strong>
+  }
+  &lt;/gcm&gt;
+&lt;/message&gt;
+</pre>
+
+<p>Here is an example of a receipt notification message that CCS sends back to your 3rd-party
+app server:</p>
+
+</p>
+<pre>&lt;message id=&quot;&quot;&gt;
+  &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
+  {
+      &quot;category&quot;:&quot;com.example.yourapp&quot;, // to know which app sent it
+      &quot;data&quot;:
+      {
+         &#x201c;message_status&quot;:&quot;MESSAGE_SENT_TO_DEVICE&quot;,
+         &#x201c;original_message_id&#x201d;:&#x201d;m-1366082849205&#x201d;
+         &#x201c;device_registration_id&#x201d;: &#x201c;REGISTRATION_ID&#x201d;
+      },
+      &quot;message_id&quot;:&quot;dr2:m-1366082849205&quot;,
+      &quot;message_type&quot;:&quot;receipt&quot;,
+      &quot;from&quot;:&quot;gcm.googleapis.com&quot;
+  }
+  &lt;/gcm&gt;
+&lt;/message&gt;</pre>
+
+<p>Note the following:</p>
+
+<ul>
+  <li>The {@code &quot;message_type&quot;} is set to {@code &quot;receipt&quot;}.
+  <li>The {@code &quot;message_status&quot;} is set to {@code &quot;MESSAGE_SENT_TO_DEVICE&quot;},
+  indicating that the message was delivered. Notice that in this case,
+{@code &quot;message_status&quot;} is not a field but rather part of the data payload.</li>
+  <li>The receipt message ID consists of the original message ID, but with a
+<code>dr:</code> prefix. Your 3rd-party app server must send an ACK back with this ID,
+which in this example is {@code dr2:m-1366082849205}.</li>
+  <li>The original message ID and status are inside the
+{@code &quot;data&quot;} field.</li>
+</ul>
+
 <h2 id="flow">Flow Control</h2>
 
 <p>Every message sent to CCS receives either an ACK or a NACK response. Messages
diff --git a/docs/html/google/gcm/client.jd b/docs/html/google/gcm/client.jd
index ac446dc..20bff10 100644
--- a/docs/html/google/gcm/client.jd
+++ b/docs/html/google/gcm/client.jd
@@ -246,7 +246,8 @@
 <h3 id="sample-register">Register for GCM</h3>
 <p>An Android application needs to register with GCM servers before it can receive
 messages. When an app registers, it receives a registration ID, which it can then
-store for future use. In the following snippet the {@code onCreate()} method in the sample app's
+store for future use (note that registration IDs must be kept secret). In the
+following snippet the {@code onCreate()} method in the sample app's
 main activity checks to see if the app is already registered with GCM and with
 the server:</p>
 
diff --git a/docs/html/google/gcm/gcm.jd b/docs/html/google/gcm/gcm.jd
index 88bf659..19151b9 100644
--- a/docs/html/google/gcm/gcm.jd
+++ b/docs/html/google/gcm/gcm.jd
@@ -123,7 +123,7 @@
 it to the 3rd-party application server, which uses it to identify each device 
 that has registered to receive messages for a given Android application. In other words,
 a registration ID is tied to a particular Android application running on a particular
-device.
+device. Note that registration IDs must be kept secret.
 <br/>
 <br/>
 <strong>Note:</strong> If you use 
diff --git a/docs/html/google/gcm/index.jd b/docs/html/google/gcm/index.jd
index 70f7a9c..56e0865 100644
--- a/docs/html/google/gcm/index.jd
+++ b/docs/html/google/gcm/index.jd
@@ -14,7 +14,10 @@
   <h1 itemprop="name" style="margin-bottom:0;">Google Cloud Messaging for Android</h1>
   <p itemprop="description">
   Google Cloud Messaging for Android (GCM) is a service that allows you to send data
-from your server to your users' Android-powered device, and also to receive messages from devices on the same connection. The GCM service handles all aspects of queueing of messages and delivery to the target Android application running on the target device. GCM is completely free no matter how big your messaging needs are, and there are no quotas.
+from your server to your users' Android-powered device, and also to receive messages from
+devices on the same connection. The GCM service handles all aspects of queueing of messages
+and delivery to the target Android application running on the target device. GCM is
+completely free no matter how big your messaging needs are, and there are no quotas.
 </p>
 
 </div>
@@ -27,31 +30,39 @@
     <p>This could be a lightweight
 message telling your app there is new data to be fetched from the
 server (for instance, a movie uploaded by a friend), or it could be a message containing
-up to 4kb of payload data (so apps like instant messaging can consume the message directly). <a href="{@docRoot}google/gcm/gcm.html">GCM Architectural Overview.</a></p>
+up to 4kb of payload data (so apps like instant messaging can consume the message directly).
+<a href="{@docRoot}google/gcm/gcm.html">GCM Architectural Overview.</a></p>
 
     <h4>Send "send-to-sync" messages</h4>
-    <p>A send-to-sync (collapsible) message is often a "tickle" that tells a mobile application to sync data from the server. For example, suppose you have an email application. When a user receives new email on the server, the server pings the mobile application with a "New mail" message. This tells the application to sync to the server to pick up the new email.
-    <a href="{@docRoot}google/gcm/adv.html#s2s">Send-to-sync messages</a>.</p>
-    </a>
+    <p>A send-to-sync (collapsible) message is often a "tickle" that tells a mobile
+    application to sync data from the server. For example, suppose you have an email
+    application. When a user receives new email on the server, the server pings the mobile
+    application with a "New mail" message. This tells the application to sync to the server
+    to pick up the new email.
+    <a href="{@docRoot}google/gcm/adv.html#s2s">Learn more &raquo;</a></p>
 
     <h4>Send messages with payload</h4>
-    <p>Unlike a send-to-sync message, every "message with payload" (non-collapsible message) is delivered. The payload the message contains can be up to 4kb.
-    <a href="{@docRoot}google/gcm/adv.html#payload">Messages with payload</a>.</p>
+    <p>Unlike a send-to-sync message, every "message with payload" (non-collapsible message)
+    is delivered. The payload the message contains can be up to 4kb.
+    <a href="{@docRoot}google/gcm/adv.html#payload">Learn more &raquo;</a></p>
   </div>
 
 
   <div class="col-6 normal-links">
     <h3 style="clear:left">New Features</h3>
-    <h4>Faster, easier GCM setup</h4>
-    <p>Streamlined registration makes it simple and fast to add GCM support to your Android app. <a href="{@docRoot}google/gcm/gs.html">Learn more &raquo;</a></p>
-    <h4>Upstream messaging over XMPP</h4>
-    <p>GCM's Cloud Connection Service (CCS) lets you communicate with Android devices over a persistent XMPP connection. The primary advantages of CCS are speed, and the ability to receive upstream messages (that is, messages from a device to the cloud). You can use the service in tandem with existing GCM APIs. Use <a href="https://services.google.com/fb/forms/gcm/">this form</a> to sign up for CCS. <a href="{@docRoot}google/gcm/ccs.html">Learn more &raquo;</a></p>
 
-    <h4>Seamless multi-device messaging</h4>
-    <p>Maps a single user to a notification key, which you can then use to send a single message to multiple devices owned by the user. Use <a href="https://services.google.com/fb/forms/gcm/">this form</a> to sign up for User Notifications. <a href="{@docRoot}google/gcm/notifications.html">Learn more &raquo;</a></p>
+
+
+    <h4>Return Receipts</h4>
+    <p>You can use upstream messaging to get receipt notifications, confirming that a given
+    message was sent to a device. Your 3rd-party app server receives the receipt notification
+    from CCS once the message has been sent to the device.
+    <a href="{@docRoot}google/gcm/ccs.html#receipts">Learn more &raquo;</a></p>
+
 
    <h4>Get Started</h4>
-    <p>Get started using the new features with a tutorial that walks you through creating a GCM app. <a href="{@docRoot}google/gcm/gs.html">Learn more &raquo;</a></p>
+    <p>Get started with a tutorial that walks you through creating a GCM app.
+    <a href="{@docRoot}google/gcm/gs.html">Learn more &raquo;</a></p>
   </div>
 
 </div>
diff --git a/docs/html/google/gcm/notifications.jd b/docs/html/google/gcm/notifications.jd
index 43a7368..2815f3d 100644
--- a/docs/html/google/gcm/notifications.jd
+++ b/docs/html/google/gcm/notifications.jd
@@ -14,8 +14,8 @@
 <h2>In this document</h2>
 
 <ol class="toc">
-  <li><a href="#request">Request Format</a></li>
-  <li><a href="#create">Generate a Notification Key</a></li>
+  <li><a href="#gen-server">Generate a Notification Key on the Server</a></li>
+  <li><a href="#gen-client">Generate a Notification Key on the Client</a></li>
   <li><a href="#add">Add Registration IDs</a></li>
   <li><a href="#remove">Remove Registration IDs</a></li>
   <li><a href="#upstream">Send Upstream Messages</a></li>
@@ -31,15 +31,11 @@
 
 <ol class="toc">
 <li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li>
-<li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" target="_android">CCS and User Notifications Signup Form</a></li>
 </ol>
 
 </div>
 </div>
 
-<p class="note"><strong>Note:</strong> To try out this feature, sign up using <a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
-
-
 <p>With user notifications, 3rd-party app servers can send a single message to
 multiple instance of an app running on devices owned by a single user. This feature
 is called <em>user notifications</em>. User notifications make it possible for every
@@ -76,27 +72,23 @@
 <p>You can use this feature with either the <a href="ccs.html">XMPP</a> (CCS) or
 <a href="http.html">HTTP</a> connection server.</p>
 
+<p>You can generate notification keys in two different ways: on the server, and on
+the client, if the user has a Google account. All of the associated registration IDs
+can be mapped to a single user.</p>
 
 <p>The examples below show you how to perform generate/add/remove operations,
 and how to send upstream messages. For generate/add/remove operations, the
 message body is JSON.</p>
 
-<h2 id="request">Request Format</h2>
-<p>To send a  message, the application server issues a POST request to
-<code>https://android.googleapis.com/gcm/notification</code>.</p>
+<h2 id="gen-server">Generate a Notification Key on the Server</h2>
 
-<p>Here is the HTTP request header you should use for all create/add/remove operations:</p>
-
-<pre>content-type: "application/json"
-Header : "project_id": &lt;projectID&gt;
-Header: "Authorization", "key=API_KEY"
-</pre>
-
-<h2 id="create">Generate a Notification Key</h2>
+<p>To generate a notification key on the server, you create a new
+create a new <code>notification_key</code> and map it to a
+<code>notification_key_name</code>.</p>
 
 <p>This example shows how to create a new <code>notification_key</code> for a
 <code>notification_key_name</code> called <code>appUser-Chris</code>.
-The {@code notification_key_name} is a name or identifier (can be a username for
+The {@code notification_key_name} is a name or identifier (it can be a username for
 a 3rd-party app) that is unique to a given user. It is used by third parties to
 group together registration IDs for a single user. Note that <code>notification_key_name</code>
 and <code>notification_key</code> are unique to a group of registration IDs. It is also
@@ -116,10 +108,109 @@
    &quot;registration_ids&quot;: [&quot;4&quot;, &quot;8&quot;, &quot;15&quot;, &quot;16&quot;, &quot;23&quot;, &quot;42&quot;]
 }</pre>
 
+<h3 id="request-server">Request format</h3>
+
+<p>To send a message in cases where your notification key is generated on the server,
+the application server issues a POST request to
+<code>https://android.googleapis.com/gcm/notification</code>.</p>
+
+<p>Here is the HTTP request header you should use for all server side create/add/remove operations:</p>
+
+<pre>content-type: "application/json"
+Header : "project_id": &lt;projectID&gt;
+Header: "Authorization", "key=API_KEY"
+</pre>
+
+
+<h2 id="gen-client">Generate a Notification Key on the Client</h2>
+
+<p>Generating a notification key on the client is useful for cases where a server is unavailable.
+To generate a notification key on the client, the device must have at least one
+Google account. Note that the process for generating a notification key on the client is significantly
+different from the server process described above.</p>
+
+<p>To generate a notification key on the client:</p>
+
+<ol>
+  <li>Open your project in the <a href="https://cloud.google.com/console">Google Developers Console</a>.</li>
+  <li>Click <strong>APIS &amp; AUTH &gt; Credentials</strong>.</li>
+  <li>Under OAuth, click <strong>Create new Client ID</strong>.</li>
+  <li>In the <strong>Create Client ID</strong> dialog, select <strong>Web Application</strong> as
+the application type, and click <strong>Create Client ID</strong>.</li>
+  <li>Copy the value from <strong>Client ID for web application &gt; Client ID</strong>.
+This client ID represents a Google account "scope" that you will use to generate an {@code id_token}.</li>
+</ol>
+
+<p>Once you've followed the above steps and gotten a client ID from Google Developers Console,
+ you're ready to add this feature to your app. First check the device for the presence of a Google
+account. For example:</p>
+
+<pre>// This snippet takes the simple approach of using the first returned Google account,
+// but you can pick any Google account on the device.
+public String getAccount() {
+    Account[] accounts = AccountManager.get(getActivity()).
+        getAccountsByType(&quot;com.google&quot;);
+    if (accounts.length == 0) {
+        return null;
+    }
+    return accounts[0].name;
+}</pre>
+
+<p>Next, get an authentication token ({@code id_token}) by using the <code><a href=
+"http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html">GoogleAuthUtil</a></code>
+class. For example:</p>
+
+<pre>String accountName = getAccount();
+
+// Initialize the scope using the client ID you got from the Console.
+final String scope = &quot;audience:server:client_id:&quot;
+        + &quot;1262xxx48712-9qs6n32447mcj9dirtnkyrejt82saa52.apps.googleusercontent.com&quot;;
+String id_token = null;
+try {
+    id_token = GoogleAuthUtil.getToken(context, accountName, scope);
+} catch (Exception e) {
+    log(&quot;exception while getting id_token: &quot; + e);
+}
+...</pre>
+
+<p>Now use <code>id_token</code> to authenticate your request.
+This add operation returns a {@code notification_key}.
+Third parties must save this {@code notification_key} (as well as its mapping to the
+<code>notification_key_name</code>)
+to use in subsequent operations. Note that a client request only takes a single regID.
+The only operations supported on the client side are add/remove.</p>
+
+<pre>request:
+{
+   &quot;operation&quot;: &quot;add&quot;,
+   &quot;notification_key_name&quot;: &quot;appUser-Chris&quot;,
+   &quot;registration_ids&quot;: [&quot;4&quot;]
+   &quot;id_token&quot;: &quot;id_token&quot;
+}</pre>
+
+<h3 id="request-client">Request format</h3>
+
+<p>To send a message in cases where your notification key is generated on the client,
+the application server issues a POST request to
+<code>https://android.googleapis.com/gcm/googlenotification</code>.</p>
+
+<p>Here is the HTTP request header you should use for all add/remove operations. The
+client side doesn't support the create operation;
+the add operation has the effect of creating the notification key if it doesn't already
+exist:</p>
+
+<pre>content-type: "application/json"
+Header : "project_id": &lt;projectID&gt;
+</pre>
+
+<p>Note that the authentication token is passed in the JSON body as shown above, not the header.
+This is different from the server case.</p>
+
+
 <h2 id="add">Add Registration IDs</h2>
 
 <p>This example shows how to add registration IDs for a given notification key.
-The maximum number of members allowed for a {@code notification_key} is 10.</p>
+The maximum number of members allowed for a {@code notification_key} is 20.</p>
 
 <p>Note that the <code>notification_key_name</code> is not strictly required for
 adding/removing regIDs. But including it protects you against accidentally using
diff --git a/docs/html/google/gcm/server.jd b/docs/html/google/gcm/server.jd
index ccd1267..e3a6b25 100644
--- a/docs/html/google/gcm/server.jd
+++ b/docs/html/google/gcm/server.jd
@@ -120,7 +120,8 @@
   <li>Able to store the API key and client registration IDs. The
 API key is included in the header of POST requests that send
 messages.</li>
- <li>Able to generate message IDs to uniquely identify each message it sends.</li>
+ <li>Able to generate message IDs to uniquely identify each message it sends. Message IDs
+should be unique per sender ID.</li>
 </ul>
 
 <h2 id="send-msg">Sending Messages</h2>
diff --git a/docs/html/google/gcs/gcs-signup.jd b/docs/html/google/gcs/gcs-signup.jd
new file mode 100644
index 0000000..7334cec
--- /dev/null
+++ b/docs/html/google/gcs/gcs-signup.jd
@@ -0,0 +1,10 @@
+page.title=Sign Up for Google Cloud Save
+
+@jd:body
+
+<p>Sign up to be a trial partner for Google Cloud Save.</p>
+
+
+<iframe src="https://docs.google.com/a/google.com/forms/d/1_V67YIXzLDLb-UzxOOpSjUDuJFfeYg3hEUT0oliK2ck/viewform?embedded=true" width="100%" height="930" frameborder="0" marginheight="0" marginwidth="0" id="signupform">Loading...</iframe>
+</body>
+</html>
diff --git a/docs/html/google/gcs/index.jd b/docs/html/google/gcs/index.jd
new file mode 100644
index 0000000..e5f4776
--- /dev/null
+++ b/docs/html/google/gcs/index.jd
@@ -0,0 +1,30 @@
+page.title=Google Cloud Save
+page.tags="gcs"
+header.hide=1
+@jd:body
+
+
+<div class="landing-banner">
+
+<div class="col-5" style="min-height:100px">
+  <img src="{@docRoot}images/google/gcs.png" />
+</div>
+<div class="col-7">
+
+  <h1 itemprop="name" style="margin-bottom:0;">Google Cloud Save</h1>
+  <p itemprop="description">
+  Google Cloud Save is a service that enables per-user data storage
+and sync in your apps with no backend programming required. Google Cloud Save
+stores its data
+in <a href="http://developers.google.com/datastore/">Google Cloud Datastore</a>,
+ a fully managed, schemaless database for storing non-relational data. Cloud
+Datastore automatically scales with your users.
+Google Cloud Save works even when your device is offline, and it
+provides an easy transition to server-side coding because
+the same database is accessible via App Engine and Compute Engine.
+Finally, Google Cloud Save provides a generous initial per-user free quota that
+expands as your user base grows.
+</p>
+<a href="{@docRoot}google/gcs/gcs-signup.html" class="button">Sign Up</a>
+</div>
+</div>
diff --git a/docs/html/google/google_toc.cs b/docs/html/google/google_toc.cs
index 6ff00c0..b4028bd 100644
--- a/docs/html/google/google_toc.cs
+++ b/docs/html/google/google_toc.cs
@@ -3,11 +3,11 @@
      ########            ATTENTION             ###############
      ########                                  ###############
      #########################################################
-     
+
      IF YOU MAKE CHANGES TO THIS FILE, YOU MUST GENERATE THE
      GMS REFERENCE DOCS, BECAUSE THEY ARE NOT INCLUDED IN THE
      DOCS BUILD RULE.
-     
+
      #########################################################
      #########################################################
 ?>
@@ -73,11 +73,11 @@
     </div>
     <ul>
       <li><a href="<?cs var:toroot?>google/play-services/setup.html">
-          <span class="en">Setup</span></a>
+          <span class="en">Setting Up Google Play Services</span></a>
       </li>
       <li class="nav-section">
         <div class="nav-section-header"><a href="<?cs var:toroot?>google/auth/api-client.html">
-          <span class="en">Accessing Google Play Services APIs</span></a>
+          <span class="en">Accessing Google APIs</span></a>
         </div>
         <ul>
           <li>
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index 2e97d62..4778a85 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -89,6 +89,16 @@
     to use Google Cloud Messaging.</p>
 </div>
 
+<div class="landing-cell">
+  <div class="cell-icon">
+  <img src="{@docRoot}images/google/gcs-small.png" width="40" >
+  </div>
+    <h4><a href="{@docRoot}google/gcs/index.html"
+    >Google Cloud Save</a></h4>
+    <p>Enable per-user data storage and sync in your apps with no backend programming
+    required.</p>
+</div>
+
 </div><!-- col-6 -->
 
 
diff --git a/docs/html/google/play-services/games.jd b/docs/html/google/play-services/games.jd
index 94f6715..a73f688 100644
--- a/docs/html/google/play-services/games.jd
+++ b/docs/html/google/play-services/games.jd
@@ -1,4 +1,5 @@
 page.title=Google Play Game Services
+page.tags="games"
 header.hide=1
 
 @jd:body
diff --git a/docs/html/google/play-services/index.jd b/docs/html/google/play-services/index.jd
index f5d4574..eec4f5a 100644
--- a/docs/html/google/play-services/index.jd
+++ b/docs/html/google/play-services/index.jd
@@ -11,10 +11,8 @@
 <div class="col-6">
 
   <h1 itemprop="name" style="margin-bottom:0;">Google Play Services</h1>
-  <p itemprop="description">
-  </p>
-
-  <p>Give your apps more features to attract users on a wider range of devices.
+  <p itemprop="description">Give your apps more features to attract users
+  on a wider range of devices.
   With Google Play services, your app can take advantage
   of the latest, Google-powered features such as Maps, Google+, and more,
   with automatic platform updates distributed as an APK through
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index d502e8d..4332953 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -1,4 +1,4 @@
-page.title=Set Up Google Play Services SDK
+page.title=Setting Up Google Play Services
 @jd:body
 
 
@@ -8,8 +8,7 @@
 
 <h2>In this document</h2>
 <ol>
-  <li><a href="#Install">Install the Google Play Services SDK</a></li>
-  <li><a href="#Setup">Set Up a Project that Uses Google Play Services</a></li>
+  <li><a href="#Setup">Add Google Play Services to Your Project</a></li>
   <li><a href="#Proguard">Create a Proguard Exception</a></li>
   <li><a href="#ensure">Ensure Devices Have the Google Play services APK</a></li>
 </ol>
@@ -18,12 +17,17 @@
 </div>
 </div>
 
+<script>
+$(document).ready(function() {
+  setupIdeDocToggle();
+});
+</script>
 
-    
+
 <p>To develop an app using the <a href="{@docRoot}reference/gms-packages.html">Google
-Play services APIs</a>, you must download the Google Play services SDK
-from the <a href="{@docRoot}tools/help/sdk-manager.html">SDK Manager</a>.
-The download includes the client library and code samples.</p>
+Play services APIs</a>, you need to set up your project with the Google Play services SDK.
+<p>If you haven't installed the Google Play services SDK yet, go get it now by following the guide
+to <a href="{@docRoot}sdk/installing/adding-packages.html">Adding SDK Packages</a>.</p>
 
 <p>To test your app when using the Google Play services SDK, you must use either:</p>
 <ul>
@@ -33,69 +37,28 @@
   that runs the Google APIs platform based on Android 4.2.2 or higher.</li>
 </ul>
 
-<p>Ideally, you should develop and test your app on a variety of devices, including
-both phones and tablets.</p>
 
 
-<h2 id="Install">Install the Google Play Services SDK</h2>
+<h2 id="Setup">Add Google Play Services to Your Project</h2>
 
-<p>To install the Google Play services SDK for development:</p>
+<p>
+<select class="ide">
+  <option value="eclipse">Using Eclipse with ADT</option>
+  <option value="studio">Using Android Studio</option>
+  <option value="other">Using something else</option>
+</select>
+</p>
+
+
+<div class="select-ide studio">
 
 <ol>
-  <li>Launch the SDK Manager in one of the following ways:
-   <ul>
-    <li>In Android Studio, click <strong>SDK Manager</strong>
-<img src="{@docRoot}images/tools/sdk-manager-studio.png" style="vertical-align:bottom;margin:0;height:19px" />
-in the toolbar.</li>
-    <li>In Eclipse (with <a href="{@docRoot}tools/help/adt.html">ADT</a>),
-    select <strong>Window</strong> &gt; <strong>Android SDK Manager</strong>.</li>
-    <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>
-    </ul>
-  </li>
-  <li>Install the Google Play services SDK.
-    <p>Scroll to the bottom of the package list, expand <b>Extras</b>, select
-    <b>Google Play services</b>, and install it. If you're using Android Studio, also install
-    <b>Google Repository</b> (it provides the Maven repository used for Gradle builds).</p>
-      <p>The Google Play services SDK is saved in your Android SDK environment at
-      <code>&lt;android-sdk&gt;/extras/google/google_play_services/</code>.</p>
-
-<p class="note"><strong>Note:</strong> Google Play services 4.0.30 (released
-November 2013) and newer versions require Android 2.3 or higher. If your app supports Android 2.2,
-you can continue development with the Google Play services SDK, but must instead install
-<b>Google Play services for Froyo</b> from the SDK Manager.</p>
-
-  </li>
-  <li>Install a compatible version of the Google APIs platform.
-    <p>If you want to test your app on the emulator, expand the directory for <b>Android 4.2.2
-    (API 17)</b> or a higher version, select <b>Google APIs</b>, and install it. Then create a
-    new <a href="{@docRoot}tools/devices/index.html">AVD</a> with Google APIs as
-    the platform target.</p>
-  </li>
-  <li>Make a copy of the Google Play services library project.
-    <p class="note"><strong>Note:</strong> If you are using Android Studio, skip this step.</p>
-    <p>Copy the library project at
-  <code>&lt;android-sdk&gt;/extras/google/google_play_services/libproject/google-play-services_lib/</code>        
-  to the location where you maintain your Android app projects.
-  <p>If you are using Eclipse, import the library project into your workspace.
-  Click <b>File > Import</b>, select <b>Android > Existing
-  Android Code into Workspace</b>, and browse to the copy of the library project to import it.</p>
-  </li>
-</ol>
-
-
-
-<h2 id="Setup">Set Up a Project that Uses Google Play Services</h2>
-
-<p><b>Using Android Studio:</b></p>
-
-<ol>
-  <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>
+  <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. 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">
@@ -126,8 +89,11 @@
 <p>You can now begin developing features with the
 <a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
 
+</div><!-- end studio -->
 
-<p><b>Using Eclipse or another IDE:</b></p>
+
+
+<div class="select-ide eclipse other">
 
 <p>To make the Google Play services APIs available to your app, you must reference the library
 project you created in step 4 of the <a href="#Install">installation instructions</a>.</p>
@@ -154,6 +120,9 @@
 you can begin developing features with the
 <a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
 
+</div><!-- end eclipse and other -->
+
+
 
 
 <h2 id="Proguard">Create a Proguard Exception</h2>
@@ -234,5 +203,6 @@
 that takes the user to Google Play Store to install the update.</p>
 
 
-<p>To then begin a connection to Google Play services, read <a
-href="{@docRoot}google/auth/api-client.html">Accessing Google Play Services APIs</a>.</p>
+<p>To then begin a connection to Google Play services (required by most Google APIs such
+as Google Drive, Google+, and Games), read <a
+href="{@docRoot}google/auth/api-client.html">Accessing Google APIs</a>.</p>
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/activities.jd b/docs/html/guide/components/activities.jd
index b4617fb..5e6917b 100644
--- a/docs/html/guide/components/activities.jd
+++ b/docs/html/guide/components/activities.jd
@@ -4,12 +4,6 @@
 
 <div id="qv-wrapper">
 <div id="qv">
-<h2>Quickview</h2>
-<ul>
-  <li>An activity provides a user interface for a single screen in your application</li>
-  <li>Activities can move into the background and then be resumed with their state restored</li>
-</ul>
-
 <h2>In this document</h2>
 <ol>
   <li><a href="#Creating">Creating an Activity</a>
diff --git a/docs/html/guide/components/bound-services.jd b/docs/html/guide/components/bound-services.jd
index 653c7a0..4215f0f2 100644
--- a/docs/html/guide/components/bound-services.jd
+++ b/docs/html/guide/components/bound-services.jd
@@ -6,12 +6,6 @@
 
 <div id="qv-wrapper">
 <ol id="qv">
-<h2>Quickview</h2>
-<ul>
-  <li>A bound service allows other components to bind to it, in order to interact with it and
-perform interprocess communication</li>
-  <li>A bound service is destroyed once all clients unbind, unless the service was also started</li>
-</ul>
 <h2>In this document</h2>
 <ol>
   <li><a href="#Basics">The Basics</a></li>
diff --git a/docs/html/guide/components/fragments.jd b/docs/html/guide/components/fragments.jd
index 32c9f99..0cc5f72 100644
--- a/docs/html/guide/components/fragments.jd
+++ b/docs/html/guide/components/fragments.jd
@@ -5,15 +5,6 @@
 
 <div id="qv-wrapper">
 <div id="qv">
-
-  <h2>Quickview</h2>
-  <ul>
-    <li>Fragments decompose application functionality and UI into reusable modules</li>
-    <li>Add multiple fragments to a screen to avoid switching activities</li>
-    <li>Fragments have their own lifecycle, state, and back stack</li>
-    <li>Fragments require API Level 11 or greater</li>
-  </ul>
-
   <h2>In this document</h2>
   <ol>
     <li><a href="#Design">Design Philosophy</a></li>
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/components/processes-and-threads.jd b/docs/html/guide/components/processes-and-threads.jd
index c8c3764..e7ef7ba 100644
--- a/docs/html/guide/components/processes-and-threads.jd
+++ b/docs/html/guide/components/processes-and-threads.jd
@@ -5,13 +5,6 @@
 
 <div id="qv-wrapper">
 <div id="qv">
-<h2>Quickview</h2>
-<ul>
-  <li>Every application runs in its own process and all components of the application run in that
-process, by default</li>
-  <li>Any slow, blocking operations in an activity should be done in a new thread, to avoid slowing
-down the user interface</li>
-</ul>
 
 <h2>In this document</h2>
 <ol>
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index da01d2c..6e22be8 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -3,14 +3,6 @@
 
 <div id="qv-wrapper">
 <ol id="qv">
-<h2>Quickview</h2>
-<ul>
-  <li>A service can run in the background to perform work even while the user is in a different
-application</li>
-  <li>A service can allow other components to bind to it, in order to interact with it and
-perform interprocess communication</li>
-  <li>A service runs in the main thread of the application that hosts it, by default</li>
-</ul>
 <h2>In this document</h2>
 <ol>
 <li><a href="#Basics">The Basics</a></li>
diff --git a/docs/html/guide/components/tasks-and-back-stack.jd b/docs/html/guide/components/tasks-and-back-stack.jd
index f818873..e054313 100644
--- a/docs/html/guide/components/tasks-and-back-stack.jd
+++ b/docs/html/guide/components/tasks-and-back-stack.jd
@@ -5,14 +5,6 @@
 
 <div id="qv-wrapper">
 <div id="qv">
-<h2>Quickview</h2>
-<ul>
-  <li>All activities belong to a task</li>
-  <li>A task contains a collection of activities in the order in which the user interacts with
-them</li>
-  <li>Tasks can move to the background and retain the state of each activity in order for users
-to perform other tasks without losing their work</li>
-</ul>
 
 <h2>In this document</h2>
 <ol>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 0a234aa..ea36405 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -373,6 +373,9 @@
             <li><a href="<?cs var:toroot ?>guide/topics/media/mediarouteprovider.html">
                   <span class="en">Media Route Provider</span></a>
                 </li>
+            <li><a href="<?cs var:toroot ?>guide/topics/media/exoplayer.html">
+                  <span class="en">ExoPlayer</span></a>
+                </li>
             <li><a href="<?cs var:toroot ?>guide/appendix/media-formats.html">
                    <span class="en">Supported Media Formats</span></a>
                 </li>
@@ -564,7 +567,11 @@
       <li><a href="<?cs var:toroot ?>guide/practices/tablets-and-handsets.html">
             <span class="en">Supporting Tablets and Handsets</span>
           </a></li>
-
+      <li>
+        <a href="<?cs var:toroot ?>guide/practices/verifying-apps-art.html">
+          <span class="en">Verifying App Behavior on ART</span>
+        </a>
+      </li>
     </ul>
   </li>
 
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..8a88222
--- /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("java.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/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd
index b648d48..c4d5083 100644
--- a/docs/html/guide/topics/manifest/activity-element.jd
+++ b/docs/html/guide/topics/manifest/activity-element.jd
@@ -5,7 +5,8 @@
 
 <dl class="xml">
 <dt>syntax:</dt>
-<dd><pre class="stx">&lt;activity android:<a href="#reparent">allowTaskReparenting</a>=["true" | "false"]
+<dd><pre class="stx">&lt;activity android:<a href="#embedded">allowEmbedded</a>=["true" | "false"]
+          android:<a href="#reparent">allowTaskReparenting</a>=["true" | "false"]
           android:<a href="#always">alwaysRetainTaskState</a>=["true" | "false"]
           android:<a href="#clear">clearTaskOnLaunch</a>=["true" | "false"]
           android:<a href="#config">configChanges</a>=["mcc", "mnc", "locale",
@@ -62,6 +63,17 @@
 
 <dt>attributes:</dt>
 <dd><dl class="attr">
+<dt><a name="embedded"></a>{@code android:allowEmbedded}</dt>
+<dd>
+    Indicate that the activity can be launched as the embedded child of another
+    activity. Particularly in the case where the child lives in a container
+    such as a Display owned by another activity. For example, activities
+    that are used for Wear custom notifications must declare this so
+    Wear can display the activity in it's context stream, which resides
+    in another process.
+
+    <p>The default value of this attribute is <code>false</code>.
+</dd>
 <dt><a name="reparent"></a>{@code android:allowTaskReparenting}</dt>
 <dd>Whether or not the activity can move from the task that started it to
 the task it has an affinity for when that task is next brought to the
diff --git a/docs/html/guide/topics/media/exoplayer.jd b/docs/html/guide/topics/media/exoplayer.jd
new file mode 100644
index 0000000..17b4669
--- /dev/null
+++ b/docs/html/guide/topics/media/exoplayer.jd
@@ -0,0 +1,514 @@
+page.title=ExoPlayer
+page.tags="audio","video","adaptive","streaming","DASH","smoothstreaming"
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+    <h2>In this document</h2>
+    <ol>
+      <li><a href="#overview">Overview</a></li>
+      <li><a href="#trackrenderer">TrackRenderer</a></li>
+      <li><a href="#samplesource">SampleSource</a>
+        <ol>
+          <li><a href="#mediaextractor">Providing media using MediaExtractor</a></li>
+          <li><a href="#adaptive-playback">Providing media for adaptive playback</a>
+            <ol>
+              <li><a href="#format-selection">Format selection for adaptive playback</a></li>
+            </ol>
+          </li>
+        </ol>
+      <li><a href="#events">Player Events</a>
+        <ol>
+          <li><a href="#high-events">High level events</a></li>
+          <li><a href="#low-events">Low level events</a></li>
+        </ol>
+      </li>
+      <li><a href="#sending-messages">Sending messages to components</a></li>
+      <li><a href="#customizing">Customizing ExoPlayer</a>
+        <ol>
+          <li><a href="#custom-guidelines">Custom component guidelines</a></li>
+        </ol>
+      </li>
+      <li><a href="#drm">Digital Rights Management</a></li>
+    </ol>
+    <h2>Key Classes</h2>
+    <ol>
+      <li>{@link android.media.MediaCodec}</li>
+      <li>{@link android.media.MediaExtractor}</li>
+      <li>{@link android.media.AudioTrack}</li>
+    </ol>
+    <h2>Related Samples</h2>
+    <ol>
+      <li><a class="external-link" href="https://github.com/google/ExoPlayer">
+        ExoPlayer Project</a></li>
+      <li><a class="external-link" href="http://google.github.io/ExoPlayer/doc/reference/packages.html">
+        Class Reference</a></li>
+    </ol>
+  </div>
+</div>
+
+
+<p>Playing videos and music is a popular activity on Android devices. The Android framework
+  provides {@link android.media.MediaPlayer} as a quick solution for playing media with minimal
+  code, and the {@link android.media.MediaCodec} and {@link android.media.MediaExtractor} classes
+  are provided for building custom media players. The open source project, ExoPlayer, is a
+  solution between these two options, providing a pre-built player that you can extend.</p>
+
+<p>ExoPlayer supports features not currently provided by
+  {@link android.media.MediaPlayer}, including Dynamic adaptive streaming
+  over HTTP (DASH), SmoothStreaming, and persistent caching. ExoPlayer can be extended
+  to handle additional media formats, and because you include it as part of your app code,
+  you can update it along with your app.</p>
+
+<p>This guide describes how to use ExoPlayer for playing Android supported media formats, as well as
+  DASH and SmoothStreaming playback. This guide also discusses ExoPlayer events, messages, DRM
+  support and guidelines for customizing the player.</p>
+
+<p class="note">
+  <strong>Note:</strong> ExoPlayer is an open source project that is not part of the Android
+  framework and is distributed separately from the Android SDK. The project contains a library and
+  a demo app that shows both simple and more advanced use of ExoPlayer:</p>
+
+<ul>
+    <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/library">
+      ExoPlayer Library</a> &mdash; This part of the project contains the core library classes.</li>
+    <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/demo/src/main/java/com/google/android/exoplayer/demo/simple">
+      Simple Demo</a> &mdash; This part of the app demonstrates a basic use of ExoPlayer.</li>
+    <li><a class="external-link" href="https://github.com/google/ExoPlayer/tree/master/demo/src/main/java/com/google/android/exoplayer/demo/full">
+      Full Demo</a> &mdash; This part of the app demonstrates more advanced features,
+      including the ability to select between multiple audio tracks, a background audio mode,
+      event logging and DRM protected playback. </li>
+</ul>
+
+
+<h2 id="overview">Overview</h2>
+
+<p>ExoPlayer is a media player built on top of the {@link android.media.MediaExtractor} and
+  {@link android.media.MediaCodec} APIs released in Android 4.1 (API level 16). At the core of this
+  library is the {@code ExoPlayer} class. This class maintains the player’s global state, but makes few
+  assumptions about the nature of the media being played, such as how the media data is obtained,
+  how it is buffered or its format. You inject this functionality through ExoPlayer’s {@code
+  prepare()} method in the form of {@code TrackRenderer} objects.</p>
+
+<p>ExoPlayer provides default {@code TrackRenderer} implementations for audio and
+  video, which make use of the {@link android.media.MediaCodec} and {@link android.media.AudioTrack}
+  classes in the Android framework. Both renderers require a {@code SampleSource} object, from which
+  they obtain individual media samples for playback. Figure 1 shows the high level object model for
+  an ExoPlayer implementation configured to play audio and video using these components.</p>
+
+<img src="{@docRoot}images/exoplayer/object-model.png" alt="" id="figure1" />
+<p class="img-caption">
+  <strong>Figure 1.</strong> High level object model for an ExoPlayer configured to play audio
+  and video using {@code TrackRenderer} objects
+</p>
+
+
+<h2 id="trackrenderer">TrackRenderer</h2>
+
+<p>A {@code TrackRenderer} processes a component of media for playback, such as
+  video, audio or text. The ExoPlayer class invokes methods on its {@code TrackRenderer} instances from a
+  single playback thread, and by doing so causes each media component to be rendered as the global
+  playback position is advanced. The ExoPlayer library provides {@code MediaCodecVideoTrackRenderer} as
+  the default implementations rendering video and {@code MediaCodecAudioTrackRenderer} for audio.
+  Both implementations make use of {@link android.media.MediaCodec} to decode individual media
+  samples. They can handle all audio and video formats supported by a given Android device
+  (see <a href="http://developer.android.com/guide/appendix/media-formats.html">Supported Media
+  Formats</a> for details). The ExoPlayer library also provides an implementation for rendering
+  text called {@code TextTrackRenderer}.
+</p>
+
+<p>The code example below outlines the main steps required to instantiate an ExoPlayer to play video
+  and audio using the standard {@code TrackRenderer} implementations.</p>
+
+<pre>
+// 1. Instantiate the player.
+player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
+// 2. Construct renderers.
+MediaCodecVideoTrackRenderer videoRenderer = …
+MediaCodecAudioTrackRenderer audioRenderer = ...
+// 3. Inject the renderers through prepare.
+player.prepare(videoRenderer, audioRenderer);
+// 4. Pass the surface to the video renderer.
+player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
+        surface);
+// 5. Start playback.
+player.setPlayWhenReady(true);
+...
+player.release(); // Don’t forget to release when done!
+</pre>
+
+<p>For a complete example, see the {@code SimplePlayerActivity} in the ExoPlayer demo app, which
+  correctly manages an ExoPlayer instance with respect to both the {@link android.app.Activity} and
+  {@link android.view.Surface} lifecycles.</p>
+
+
+<h2 id="samplesource">SampleSource</h2>
+
+<p>A standard {@code TrackRenderer} implementation requires a {@code SampleSource} to
+  be provided in its constructor. A {@code SampleSource} object provides format information and
+  media samples to be rendered. The ExoPlayer library provides {@code FrameworkSampleSource} and
+  {@code ChunkSampleSource}. The {@code FrameworkSampleSource} class uses {@link
+  android.media.MediaExtractor} to request, buffer and extract the media samples. The {@code
+  ChunkSampleSource} class provides adaptive playback using DASH or SmoothStreaming, and
+  implements networking, buffering and media extraction within the ExoPlayer library.</p>
+
+
+<h3 id="mediaextractor">Providing media using MediaExtractor</h3>
+
+<p>
+  In order to render media formats supported by the Android framework, the {@code
+  FrameworkSampleSource} class uses {@link android.media.MediaExtractor} for networking,
+  buffering and sample extraction functionality. By doing so, it supports any media container format
+  supported by the version of Android where it is running. For more information about media formats
+  supported by Android, see <a href="{@docRoot}guide/appendix/media-formats.html">Supported
+  Media Formats</a>.
+</p>
+
+<p>The diagram in Figure 2 shows the object model for an ExoPlayer implementation using
+  {@code FrameworkSampleSource}.</p>
+
+<img src="{@docRoot}images/exoplayer/frameworksamplesource.png" alt="" id="figure2" />
+<p class="img-caption">
+  <strong>Figure 2.</strong> Object model for an implementation of ExoPlayer that renders
+  media formats supported by Android using {@code FrameworkSampleSource}
+</p>
+
+<p>The following code example outlines how the video and audio renderers are constructed to
+  load the video from a specified URI.</p>
+
+<pre>
+FrameworkSampleSource sampleSource = new FrameworkSampleSource(
+        activity, uri, null, 2);
+MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
+        sampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0,
+        mainHandler, playerActivity, 50);
+MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
+        sampleSource, null, true);
+</pre>
+
+<p>The ExoPlayer demo app provides a complete implementation of this code in
+  {@code DefaultRendererBuilder}. The {@code SimplePlaybackActivity} class uses it to play one
+  of the videos available in the demo app. Note that in the example, video and audio
+  are muxed, meaning they are streamed together from a single URI. The {@code FrameworkSampleSource}
+  instance provides video samples to the {@code videoRenderer} object and audio samples to the
+  {@code audioRenderer} object as they are extracted from the media container format. It is also
+  possible to play demuxed media, where video and audio are streamed separately from different URIs.
+  This functionality can be achieved by having two {@code FrameworkSampleSource} instances instead
+  of one.</p>
+
+
+<h3 id="adaptive-playback">Providing media for adaptive playback</h3>
+
+<p>ExoPlayer supports adaptive streaming, which allows the quality of the
+  media data to be adjusted during playback based on the network conditions. DASH
+  and SmoothStreaming are examples of adaptive streaming technologies. Both these approaches
+  load media in small chunks (typically 2 to 10 seconds in duration). Whenever a chunk of media
+  is requested, the client selects from a number of possible formats. For example, a client may
+  select a high quality format if network conditions are good, or a low quality format if network
+  conditions are bad. In both techniques, video and audio are streamed separately.</p>
+
+<p>ExoPlayer supports adaptive playback through use of the {@code ChunkSampleSource} class,
+  which loads chunks of media data from which individual samples can be extracted. Each {@code
+  ChunkSampleSource} requires a {@code ChunkSource} object to be injected through its constructor,
+  which is responsible for providing media chunks from which to load and read samples. The {@code
+  DashMp4ChunkSource} and {@code SmoothStreamingChunkSource} classes provide DASH and SmoothStreaming
+  playback using the FMP4 container format. The {@code DashWebMChunkSource} class uses the WebM
+  container format to provide DASH playback.</p>
+
+<p>All of the standard {@code ChunkSource} implementations require a {@code FormatEvaluator} and
+  a {@code DataSource} to be injected through their constructors. The {@code FormatEvaluator}
+  objects select from the available formats before each chunk is loaded. The {@code DataSource}
+  objects are responsible for actually loading the data. Finally, the {@code ChunkSampleSources}
+  require a {@code LoadControl} object that controls the chunk buffering policy.</p>
+
+<p>The object model of an ExoPlayer configured for a DASH adaptive playback is shown in the
+  diagram below. This example uses an {@code HttpDataSource} object to stream the media over the
+  network. The video quality is varied at runtime using the adaptive implementation of {@code
+  FormatEvaluator}, while audio is played at a fixed quality level.</p>
+
+<img src="{@docRoot}images/exoplayer/adaptive-streaming.png" alt="" id="figure3" />
+<p class="img-caption">
+  <strong>Figure 3.</strong> Object model for a DASH adaptive playback using ExoPlayer
+</p>
+
+<p>The following code example outlines how the video and audio renderers are constructed.</p>
+
+<pre>
+Handler mainHandler = playerActivity.getMainHandler();
+LoadControl loadControl = new DefaultLoadControl(
+        new BufferPool(BUFFER_SEGMENT_SIZE));
+BandwidthMeter bandwidthMeter = new BandwidthMeter();
+
+// Build the video renderer.
+DataSource videoDataSource = new HttpDataSource(userAgent,
+        HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
+ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource,
+        new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
+ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource,
+        loadControl, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
+MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
+        videoSampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
+        0, mainHandler, playerActivity, 50);
+
+// Build the audio renderer.
+DataSource audioDataSource = new HttpDataSource(userAgent,
+        HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
+ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource,
+        new FormatEvaluator.FixedEvaluator(), audioRepresentation);
+SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource,
+        loadControl, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
+MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
+        audioSampleSource, null, true);
+</pre>
+
+<p>In this code, {@code videoRepresentations} and {@code audioRepresentation} are {@code
+  Representation} objects, each of which describes one of the available media streams. In the DASH
+  model, these streams are parsed from a media presentation description (MPD) file. The ExoPlayer
+  library provides a {@code MediaPresentationDescriptionParser} class to obtain {@code
+  Representation} objects from MPD files.</p>
+
+<p class="note">
+  <strong>Note:</strong> Building Representation objects from MPD files is not required. You can
+  build Representation objects from other data sources if necessary.
+</p>
+
+<p>The ExoPlayer demo app provides complete implementation of this code in
+  {@code DashVodRendererBuilder}. The {@code SimplePlaybackActivity} class uses this builder to
+  construct renderers for playing DASH sample videos in the demo app. It asynchronously fetches a
+  specified MPD file in order to construct the required {@code Representation} objects. For an
+  equivalent SmoothStreaming example, see the {@code SmoothStreamingRendererBuilder} class in the
+  demo app.</p>
+
+
+<h4 id="format-selection">Format selection for adaptive playback</h4>
+
+<p>For DASH and SmoothStreaming playback, consider both static format selection at the
+  start of playback and dynamic format selection during playback. Static format selection should be
+  used to filter out formats that should not be used throughout the playback, for example formats
+  with resolutions higher than the maximum supported by the playback device. Dynamic selection varies
+  the selected format during playback, typically to adapt video quality in response to changes in
+  network conditions.</p>
+
+<h5 id="static-selection">Static format selection</h5>
+
+<p>When preparing a player, you should consider filtering out some of the available formats if
+  they are not useable for playback. Static format selection allows you to filter out
+  formats that cannot be used on a particular device or are not compatible with your player.
+  For audio playback, this often means picking a single format to play and discarding the others.</p>
+
+<p>For video playback, filtering formats can be more complicated. Apps should first
+  eliminate any streams that whose resolution is too high to be played by the device. For H.264,
+  which is normally used for DASH and SmoothStreaming playback, ExoPlayer’s {@code MediaCodecUtil}
+  class provides a {@code maxH264DecodableFrameSize()} method that can be used to determine what
+  resolution streams the device is able to handle, as shown in the following code example:</p>
+
+<pre>
+int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
+Format format = representation.format;
+if (format.width * format.height &lt;= maxDecodableFrameSize) {
+  // The device can play this stream.
+  videoRepresentations.add(representation);
+} else {
+  // The device isn't capable of playing this stream.
+}
+</pre>
+
+<p>This approach is used to filter {@code Representations} in the {@code DashVodRendererBuilder}
+  class of the ExoPlayer demo app, and similarly to filter track indices in {@code
+  SmoothStreamingRendererBuilder}.</p>
+
+<p>In addition to eliminating unsupported formats, it should be noted that the ability to
+  seamlessly switch between H.264 streams of different resolution is an optional decoder feature
+  available in Android 4.3 (API level 16) and higher, and so is not supported by all devices. The
+  availability of an adaptive H.264 decoder can be queried using {@code MediaCodecUtil}, as shown in
+  the following code example:</p>
+
+<pre>
+boolean isAdaptive = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264).adaptive;
+</pre>
+
+<p>The {@code MediaCodecVideoTrackRenderer} class is still able to handle resolution changes on
+  devices that do not have adaptive decoders, however the switch is not seamless. Typically, the
+  switch creates a small discontinuity in visual output lasting around 50-100ms. For devices that
+  do not provide an adaptive decoder, app developers may choose to adapt between formats at
+  a single fixed resolution so as to avoid discontinuities. The ExoPlayer demo app
+  implementation does not pick a fixed resolution.</p>
+
+
+<h5 id="dynamic-selection">Dynamic format selection</h5>
+
+<p>During playback, you can use a {@code FormatEvaluator} to dynamically select from the
+  available video formats. The ExoPlayer library provides a {@code FormatEvaluator.Adaptive}
+  implementation for dynamically selecting between video formats based on the current network
+  conditions.</p>
+
+<p>This class provides a simple, general purpose reference implementation, however you are
+  encouraged to write your own {@code FormatEvaluator} implementation to best suit your particular
+  needs.</p>
+
+
+<h2 id="events">Player Events</h2>
+
+<p>During playback, your app can listen for events generated by the ExoPlayer that indicate the
+  overall state of the player. These events are useful as triggers for updating the app user
+  interface such as playback controls. Many ExoPlayer components also report their own component
+  specific low level events, which can be useful for performance monitoring.</p>
+
+
+<h3 id="high-events">High level events</h3>
+
+<p>ExoPlayer allows instances of {@code ExoPlayer.Listener} to be added and removed using its
+  {@code addListener()} and {@code removeListener()} methods. Registered listeners are notified of
+  changes in playback state, as well as when errors occur that cause playback to fail. For more
+  information about the valid playback states and the possible transitions between them, see the
+  ExoPlayer source code.</p>
+
+<p>Developers who implement custom playback controls should register a listener and use it to
+  update their controls as the player’s state changes. An app should also show an
+  appropriate error to the user if playback fails.</p>
+
+<h3 id="low-events">Low level events</h3>
+
+<p>In addition to high level listeners, many of the individual components provided by the
+  ExoPlayer library allow their own event listeners. For example, {@code
+  MediaCodecVideoTrackRenderer} has constructors that take a {@code
+  MediaCodecVideoTrackRenderer.EventListener}. In the ExoPlayer demo app, {@code SimplePlayerActivity}
+  acts as a listener so that it can adjust the dimensions of the target surface to have the correct
+  height and width ratio for the video being played:</p>
+
+<pre>
+&#64;Override
+public void onVideoSizeChanged(int width, int height) {
+  surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height);
+}
+</pre>
+
+<p>The {@code RendererBuilder} classes in the ExoPlayer demo app inject the activity as the
+  listener, for example in the {@code DashVodRendererBuilder} class:</p>
+
+<pre>
+MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
+        videoSampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
+        0, <strong>mainHandler, playerActivity</strong>, 50);
+</pre>
+
+<p>Note that you must pass a {@link android.os.Handler} object to the renderer, which determines
+  the thread on which the listener’s methods are invoked. In most cases, you should use a
+  {@link android.os.Handler} associated with the app’s main thread, as is the case in this example.
+  </p>
+
+<p>Listening to individual components can be useful for adjusting UI based on player events, as
+  in the example above. Listening to component events can also be helpful for logging performance
+  metrics. For example, {@code MediaCodecVideoTrackRenderer} notifies its listener of dropped video
+  frames. A developer may wish to log such metrics to track playback performance in their
+  app.</p>
+
+<p>Many components also notify their listeners when errors occur. Such errors may or may not
+  cause playback to fail. If an error does not cause playback to fail, it may still result in
+  degraded performance, and so you may wish to log all errors in order to track playback
+  performance. Note that an ExoPlayer instance always notifies its high level listeners of errors that
+  cause playback to fail, in addition to the listener of the individual component from which the error
+  originated. Hence, you should display error messages to users only from high level listeners.
+  Within individual component listeners, you should use error notifications only for informational
+  purposes.</p>
+
+
+<h2 id="sending-messages">Sending messages to components</h2>
+
+<p>Some ExoPlayer components allow changes in configuration during playback. By convention, you make
+  these changes by passing asynchronous messages through the ExoPlayer to the component.
+  This approach ensures both thread safety and that the configuration change is
+  executed in order with any other operations being performed on the player.</p>
+
+<p>The most common use of messaging is passing a target surface to
+  {@code MediaCodecVideoTrackRenderer}:</p>
+
+<pre>
+player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
+        surface);
+</pre>
+
+<p>Note that if the surface needs to be cleared because
+  {@link android.view.SurfaceHolder.Callback#surfaceDestroyed
+  SurfaceHolder.Callback.surfaceDestroyed()} has been invoked, then you must send this
+  message using the blocking variant of {@code sendMessage()}:</p>
+<p>
+
+<pre>
+player.blockingSendMessage(videoRenderer,
+        MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
+</pre>
+
+<p>You must use a blocking message because the contract of {@link
+  android.view.SurfaceHolder.Callback#surfaceDestroyed surfaceDestroyed()} requires that the
+  app does not attempt to access the surface after the method returns. The {@code
+  SimplePlayerActivity} class in the demo app demonstrates how the surface should be set and
+  cleared.</p>
+
+
+<h2 id="customizing">Customizing ExoPlayer</h2>
+
+<p>One of the main benefits of ExoPlayer over {@link android.media.MediaPlayer} is the ability to
+  customize and extend the player to better suit the developer’s use case. The ExoPlayer library
+  is designed specifically with this in mind, defining a number of abstract base classes and
+  interfaces that make it possible for app developers to easily replace the default implementations
+  provided by the library. Here are some use cases for building custom components:</p>
+
+<ul>
+  <li><strong>{@code TrackRenderer}</strong> - You may want to implement a custom
+    {@code TrackRenderer} to handle media types other than audio and video. The {@code
+    TextTrackRenderer} class within the ExoPlayer library is an example of how to implement a
+    custom renderer. You could use the approach it demonstrates to render custom
+    overlays or annotations. Implementing this kind of functionality as a {@code TrackRenderer}
+    makes it easy to keep the overlays or annotations in sync with the other media being played.</li>
+  <li><strong>{@code SampleSource}</strong> - If you need to support a container format not
+    already handled by {@link android.media.MediaExtractor} or ExoPlayer, consider implementing a
+    custom {@code SampleSource} class.</li>
+  <li><strong>{@code FormatEvaluator}</strong> - The ExoPlayer library provides {@code
+    FormatEvaluator.Adaptive} as a simple reference implementation that switches between different
+    quality video formats based on the available bandwidth. App developers are encouraged to
+    develop their own adaptive {@code FormatEvaluator} implementations, which can be designed to
+    suit their use specific needs.</li>
+  <li><strong>{@code DataSource}</strong> - ExoPlayer’s upstream package already contains a
+    number of {@code DataSource} implementations for different use cases, such as writing and
+    reading to and from a persistent media cache. You may want to implement you own
+    {@code DataSource} class to load data in another way, such as a custom
+    protocol or HTTP stack for data input.</li>
+</ul>
+
+
+<h3 id="custom-guidelines">Custom component guidelines</h3>
+
+<p>If a custom component needs to report events back to the app, we recommend that you
+  do so using the same model as existing ExoPlayer components, where an event listener is passed
+  together with a {@link android.os.Handler} to the constructor of the component.</p>
+
+<p>We recommended that custom components use the same model as existing ExoPlayer components to
+  allow reconfiguration by the app during playback, as described in
+  <a href="#sending-messages">Sending messages to components</a>.
+  To do this, you should implement a {@code ExoPlayerComponent} and receive
+  configuration changes in its {@code handleMessage()} method. Your app should pass
+  configuration changes by calling ExoPlayer’s {@code sendMessage()} and {@code
+  blockingSendMessage()} methods.</p>
+
+
+<h2 id="drm">Digital Rights Management</h2>
+
+<p>On Android 4.3 (API level 18) and higher, ExoPlayer supports Digital Rights Managment (DRM)
+  protected playback. In order to play DRM protected content with ExoPlayer, your app must
+  inject a {@code DrmSessionManager} into the {@code MediaCodecVideoTrackRenderer} and {@code
+  MediaCodecAudioTrackRenderer} constructors. A {@code DrmSessionManager} object is responsible for
+  providing the {@code MediaCrypto} object required for decryption, as well as ensuring that the
+  required decryption keys are available to the underlying DRM module being used.</p>
+
+<p>The ExoPlayer library provides a default implementation of {@code DrmSessionManager}, called
+  {@code StreamingDrmSessionManager}, which uses {@link android.media.MediaDrm}. The session
+  manager supports any DRM scheme for which a modular DRM component exists on the device. All
+  Android devices are required to support Widevine modular DRM (with L3 security, although many
+  devices also support L1). Some devices may support additional schemes such as PlayReady.</p>
+
+<p>The {@code StreamingDrmSessionManager} class requires a {@code MediaDrmCallback} to be
+  injected into its constructor, which is responsible for actually making provisioning and key
+  requests. You should implement this interface to make network requests to your license
+  server and obtain the required keys. The {@code WidevineTestMediaDrmCallback} class in the
+  ExoPlayer demo app sends requests to a Widevine test server.</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/exoplayer/adaptive-streaming.png b/docs/html/images/exoplayer/adaptive-streaming.png
new file mode 100644
index 0000000..9fc650c9
--- /dev/null
+++ b/docs/html/images/exoplayer/adaptive-streaming.png
Binary files differ
diff --git a/docs/html/images/exoplayer/frameworksamplesource.png b/docs/html/images/exoplayer/frameworksamplesource.png
new file mode 100644
index 0000000..fd1e314
--- /dev/null
+++ b/docs/html/images/exoplayer/frameworksamplesource.png
Binary files differ
diff --git a/docs/html/images/exoplayer/object-model.png b/docs/html/images/exoplayer/object-model.png
new file mode 100644
index 0000000..e0d6e55
--- /dev/null
+++ b/docs/html/images/exoplayer/object-model.png
Binary files differ
diff --git a/docs/html/images/google/datastore-logo.png b/docs/html/images/google/datastore-logo.png
new file mode 100644
index 0000000..a0fc0a0
--- /dev/null
+++ b/docs/html/images/google/datastore-logo.png
Binary files differ
diff --git a/docs/html/images/google/gcs-small.png b/docs/html/images/google/gcs-small.png
new file mode 100644
index 0000000..b8dbe0d
--- /dev/null
+++ b/docs/html/images/google/gcs-small.png
Binary files differ
diff --git a/docs/html/images/google/gcs.png b/docs/html/images/google/gcs.png
new file mode 100644
index 0000000..7355d64
--- /dev/null
+++ b/docs/html/images/google/gcs.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/gpfe-start-0.jpg b/docs/html/images/gpfe-start-0.jpg
index e97381d..bb68aaa 100644
--- a/docs/html/images/gpfe-start-0.jpg
+++ b/docs/html/images/gpfe-start-0.jpg
Binary files differ
diff --git a/docs/html/images/home/auto-wordmark.png b/docs/html/images/home/auto-wordmark.png
new file mode 100644
index 0000000..027dfca
--- /dev/null
+++ b/docs/html/images/home/auto-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/auto.png b/docs/html/images/home/auto.png
new file mode 100644
index 0000000..4ce882a
--- /dev/null
+++ b/docs/html/images/home/auto.png
Binary files differ
diff --git a/docs/html/images/home/tv-wordmark.png b/docs/html/images/home/tv-wordmark.png
new file mode 100644
index 0000000..e7660ac
--- /dev/null
+++ b/docs/html/images/home/tv-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/tv.png b/docs/html/images/home/tv.png
new file mode 100644
index 0000000..7208a99
--- /dev/null
+++ b/docs/html/images/home/tv.png
Binary files differ
diff --git a/docs/html/images/home/wear-wordmark.png b/docs/html/images/home/wear-wordmark.png
new file mode 100644
index 0000000..e645887
--- /dev/null
+++ b/docs/html/images/home/wear-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/wear.png b/docs/html/images/home/wear.png
new file mode 100644
index 0000000..e6602fd
--- /dev/null
+++ b/docs/html/images/home/wear.png
Binary files differ
diff --git a/docs/html/images/material.png b/docs/html/images/material.png
new file mode 100644
index 0000000..981359b
--- /dev/null
+++ b/docs/html/images/material.png
Binary files differ
diff --git a/docs/html/images/resource-card-android-studio.png b/docs/html/images/resource-card-android-studio.png
new file mode 100644
index 0000000..0f483df
--- /dev/null
+++ b/docs/html/images/resource-card-android-studio.png
Binary files differ
diff --git a/docs/html/images/tools-home.png b/docs/html/images/tools-home.png
index 291a361..86a7414 100644
--- a/docs/html/images/tools-home.png
+++ b/docs/html/images/tools-home.png
Binary files differ
diff --git a/docs/html/images/tools/android-studio.png b/docs/html/images/tools/android-studio.png
deleted file mode 100644
index 4d93a86..0000000
--- a/docs/html/images/tools/android-studio.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/images/tools/laptop-studio.png b/docs/html/images/tools/laptop-studio.png
new file mode 100644
index 0000000..3684ff0
--- /dev/null
+++ b/docs/html/images/tools/laptop-studio.png
Binary files differ
diff --git a/docs/html/images/tools/wizard2.png b/docs/html/images/tools/wizard2.png
new file mode 100644
index 0000000..921d58c
--- /dev/null
+++ b/docs/html/images/tools/wizard2.png
Binary files differ
diff --git a/docs/html/images/tools/wizard3.png b/docs/html/images/tools/wizard3.png
new file mode 100644
index 0000000..d5749d5
--- /dev/null
+++ b/docs/html/images/tools/wizard3.png
Binary files differ
diff --git a/docs/html/images/tools/wizard4.png b/docs/html/images/tools/wizard4.png
new file mode 100644
index 0000000..2709f7b
--- /dev/null
+++ b/docs/html/images/tools/wizard4.png
Binary files differ
diff --git a/docs/html/images/tools/wizard5.png b/docs/html/images/tools/wizard5.png
new file mode 100644
index 0000000..105cf2a
--- /dev/null
+++ b/docs/html/images/tools/wizard5.png
Binary files differ
diff --git a/docs/html/images/tools/wizard6.png b/docs/html/images/tools/wizard6.png
new file mode 100644
index 0000000..8b0691c
--- /dev/null
+++ b/docs/html/images/tools/wizard6.png
Binary files differ
diff --git a/docs/html/images/tools/wizard7.png b/docs/html/images/tools/wizard7.png
new file mode 100644
index 0000000..83396cf
--- /dev/null
+++ b/docs/html/images/tools/wizard7.png
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/index.jd b/docs/html/index.jd
index a4b0683..253a7a5 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -1,159 +1,100 @@
 fullpage=true
 page.viewport_width=970
-no_footer_links=true
-carousel=true
 excludeFromSuggestions=true
 page.metaDescription=The official site for Android developers. Provides the Android SDK and documentation for app developers and designers.
 page.customHeadTag=<meta name="google-site-verification" content="sa-bIAI6GKvct3f61-WpRguHq-aNjtF7xJjMTSi79as" />
 
 @jd:body
 
+<div class="home-new-carousel-1">
+  <div class="fullscreen-carousel-content">
+    <div class="vcenter">
+      <div class="wrap clearfix">
 
-<div class="wrap">
-    <!-- Slideshow -->
-    <div class="slideshow-container slideshow-home col-16">
-        <a href="" class="slideshow-prev">Prev</a>
-        <a href="" class="slideshow-next">Next</a>
-        <div class="frame">
-            <ul>
-                <!-- set explicit widths as needed to prevent overflow issues -->
+        <div class="static resource-flow-layout wrap col-16">
+          <div class="resource resource-card resource-card-18x6">
 
-
-                <li class="item carousel-home">
-                  <div class="content-left col-10" style="width:580px;">
-                    <a href="{@docRoot}wear/index.html">
-                      <img src="{@docRoot}images/home/aw_dac.png" style="margin-top:50px" >
-                    </a>
-                  </div>
-                  <div class="content-right col-5" style="width:280px;">
-                    <h1>Introducing Android Wear</h1>
-                    <p>We’re extending the Android platform to wearables. You can start building richer wearable experiences for your apps today using the enhanced Notification APIs in this Developer Preview.</p>
-                    <p>We can’t wait to see what you will create.</p>
-                    <p><a href="{@docRoot}wear/index.html" class="button">Learn more</a></p>
-                  </div>
-                </li>
-
-
-                <li class="item carousel-home">
-                    <div class="content-left col-11" style="padding-top:65px;">
-                      <script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
-                      <div style="box-shadow: 3px 10px 18px 1px #999;width:600px;height:336px">
-                        <div id="ytapiplayer">
-                          <a href="http://www.youtube.com/watch?v=WWArLD6nqrk"><img width=600 src="{@docRoot}images/video-kiwi.jpg"></a><!--You need Flash player 8+ and JavaScript enabled to view this video. -->
-                        </div>
-                        <script type="text/javascript">
-                            var params = { allowScriptAccess: "always" };
-                            var atts = { id: "ytapiplayer" };
-                            swfobject.embedSWF("//www.youtube.com/v/WWArLD6nqrk?enablejsapi=1&playerapiid=ytplayer&version=3&HD=1;rel=0;showinfo=0;modestbranding;origin=developer.android.com;autohide=1",
-                              "ytapiplayer", "600", "336", "8", null, null, params, atts);
-
-                            // Callback used to pause/resume carousel based on video state
-                            function onytplayerStateChange(newState) {
-                               var isPaused = $("#pauseButton").hasClass("paused");
-                               if ((newState == 1) || (newState == 3)) {
-                               // if playing or buffering, pause the carousel
-                                 if (!isPaused) {
-                                    $("#pauseButton").click();
-                                 }
-                               } else {
-                               // otherwise, make sure carousel is running
-                                 if (isPaused) {
-                                    $("#pauseButton").click();
-                                 }
-                               }
-                            }
-
-                            // Callback received when YouTube player loads to setup callback (above)
-                            function onYouTubePlayerReady(playerId) {
-                              var ytplayer = document.getElementById("ytapiplayer");
-                              ytplayer.addEventListener("onStateChange", "onytplayerStateChange");
-                            }
-
-                        </script>
-                      </div>
-                    </div>
-                    <div class="content-right col-4">
-                    <h1 style="white-space:nowrap;line-height:1.2em;">Developer Story: <br />Kiwi, Inc.</h1>
-                    <p>Game developer Kiwi has had five titles in the top 25 grossing on Google Play. Hear how Google Play
-                      has helped them double revenue every six months.</p>
-                      <p><a href="{@docRoot}distribute/stories/index.html" class="button">Watch more videos </a></p>
-                    </div>
-                </li>
-
-                <li class="item carousel-home">
-                  <div class="content-left col-7" style="width:400px;">
-                    <a href="{@docRoot}about/versions/kitkat.html">
-                      <img src="{@docRoot}images/home/kk-hero.jpg" width="242" style="padding-top:72px;">
-                    </a>
-                  </div>
-                  <div class="content-right col-4" style="width:340px;">
-                    <h1>Android 4.4 KitKat!</h1>
-                    <p>A new version of Android is here, with great new features, APIs, and tools for developers.</p>
-                    <p>Android 4.4 is built to run on more devices than ever before, and gives you more ways to showcase your content and create beautiful, useful, and innovative apps.</p>
-                    <p>Learn about what's new in the Platform Highlights and see the API Overview for details.</p>
-                    <p><a href="{@docRoot}about/versions/kitkat.html" class="button">Check out the highlights</a></p>
-                  </div>
-                </li>
-
-                <li class="item carousel-home">
-                  <div class="content-left col-11" style="padding-top:65px;">
-                    <a href="https://www.youtube.com/watch?v=sONcojECWXs&list=PLWz5rJ2EKKc-2quE-o0enpILZF3nBZg_K&index=1">
-                      <img src="{@docRoot}images/title-devbytes-kk.jpg" style="margin-top:0px;width:600px;">
-                    </a>
-                  </div>
-                  <div class="content-right col-4">
-                    <h1 style="white-space:nowrap;line-height:1.2em;">DevBytes: <br />Android 4.4</h1>
-                    <p>Join the DevBytes team for a look at what's new in Android 4.4 KitKat&nbsp;&mdash; new ways to make your apps beautiful, printing, storage access framework, and more.</p>
-                    <p><a href="https://www.youtube.com/watch?v=sONcojECWXs&list=PLWz5rJ2EKKc-2quE-o0enpILZF3nBZg_K&index=1" class="button">Watch the video </a></p>
-                  </div>
-                </li>
-
-                <li class="item carousel-home">
-                  <div class="content-left col-19" style="width:580px;">
-                    <a href="{@docRoot}design/patterns/new.html">
-                      <img src="{@docRoot}design/media/design_elements_landing.png" style="margin-top:30px">
-                    </a>
-                  </div>
-                  <div class="content-right col-4" style="width:280px;">
-                    <h1>Design for Android KitKat</h1>
-                    <p>Android KitKat brings a refreshed UI with updated styles, patterns, and gestures to use in your apps. </p>
-                    <p>We've updated the Android Design guidelines and added new pages on branding, fullscreen, and more. </p>
-                    <p><a href="{@docRoot}design/patterns/new.html" class="button">See what's new</a></p>
-                  </div>
-                </li>
-
-                <li class="item carousel-home">
-                  <div class="content-left col-11" style="padding-top:65px;">
-                    <a href="http://www.youtube.com/watch?v=6QHkv-bSlds&list=PLWz5rJ2EKKc8j2B95zGMb8muZvrIy-wcF&index=1">
-                      <img src="{@docRoot}images/title-adia-kk.png" style="margin-top:0px;width:600px;">
-                    </a>
-                  </div>
-                  <div class="content-right col-4">
-                    <h1 style="white-space:nowrap;line-height:1.2em;">ADIA: <br />Android 4.4</h1>
-                    </p>Join the Android Design in Action team for a walkthrough of new developer features, UX changes, and updates to design guidelines in Android 4.4.</p>
-                    <p><a href="http://www.youtube.com/watch?v=6QHkv-bSlds&list=PLWz5rJ2EKKc8j2B95zGMb8muZvrIy-wcF&index=1" class="button">Watch the video </a></p>
-                  </div>
-                </li>
-           </ul>
+      <div class="landing-section-header">
+            <div class="col-10"><img src="{@docRoot}preview/images/l-dev-prev.png"
+            style="margin:40px 60px 0 20px"></div>
+            <div class="col-5" style=" margin-top:70px ">
+            <h3 stye="font-weight:300;">L Developer Preview</h3>
+            <p>The L Developer Preview lets you design and develop against the next major
+            release of Android. Take the time to test and build your app before the platform
+            officially launches. </p>
+            <a href="{@docRoot}preview/index.html" class="landing-button landing-primary">Learn More</a>
+            </div>
+          </div>
+          </div>
         </div>
-    </div>
+       <h2>&nbsp;</h2>
+        <div style="margin-top:20px" class="resource-widget resource-flow-layout wrap col-16
+        no-section" data-query="collection:index/primary" data-resourcestyle="card"
+        data-sortorder="-timestamp" data-maxresults="3" data-cardsizes="6x2,6x2,6x2"></div> <!-- end .resource-widget -->
+      </div> <!-- end .wrap -->
+    </div> <!-- end .vcenter -->
+  </div> <!-- end .fullscreen-carousel-content -->
+</div> <!-- end .fullscreen-carousel -->
 
-<!-- /End slideshow -->
-
-    <a href="" id="pauseButton" style="display:none">pause</a>
+<div class="actions-bar" style="margin-top:20px">
+  <div class="wrap">
+    <div class="actions">
+      <div><a href="{@docRoot}sdk/index.html">Get the SDK</a></div>
+      <div><a href="{@docRoot}samples/index.html">Browse Samples</a></div>
+      <div><a href="//www.youtube.com/user/androiddevelopers">Watch Videos</a></div>
+      <div><a href="{@docRoot}distribute/googleplay/developer-console.html">Manage Your Apps</a></div>
+    </div><!-- end .actions -->
+  </div><!-- end .wrap -->
+</div><!-- end .actions-bar -->
 
 
-</div>
-<div class="wrap" style="padding-bottom:20px">
-    <!-- Section links -->
-    <div class="home-sections">
-        <ul>
-            <li><a href="{@docRoot}about/index.html">About Android</a></li>
-            <li><a href="{@docRoot}sdk/index.html">Get the SDK</a></li>
-            <li><a href="http://source.android.com">Open Source</a></li>
-            <li><a href="{@docRoot}support.html">Support</a></li>
-            <li class="last"><a href="{@docRoot}legal.html">Legal</a></li>
-        </ul>
-    </div>
-    <!-- /Section links -->
-</div>
+
+<div class="landing-rest-of-page">
+  <div class="landing-section">
+    <div class="wrap">
+      <div class="landing-section-header">
+        
+            <div class="landing-h1" style="margin-top:0px">Build for a Multi-Screen World</div>
+        <div class="landing-subhead" style="margin-top: 20px;">
+          Android runs on hundreds of millions of handheld devices around the world, <br>
+          and it now supports these exciting, new form-factors.
+        </div>
+      </div>
+      <div class="landing-body" style="margin-top: 50px;">
+        <div class="landing-breakout cols">
+          <div class="col-3-wide">
+              <img src="{@docRoot}images/home/wear-wordmark.png">
+              <img src="{@docRoot}images/home/wear.png">
+              <p class="landing-small">
+                Provide information on-the-go for your users, whenever they need it.
+            </p>
+            <p class="landing-small">
+              <a href="{@docRoot}wear/index.html">Learn about Android Wear</a>
+            </p>
+          </div>
+          <div class="col-3-wide">
+              <img src="{@docRoot}images/home/tv-wordmark.png">
+             <img src="{@docRoot}images/home/tv.png">
+              <p class="landing-small">
+                Build your apps for the big screen and bring your content to life.
+              </p>
+            <p class="landing-small">
+              <a href="{@docRoot}tv/index.html">Learn about Android TV</a>
+
+            </p>
+          </div>
+          <div class="col-3-wide">
+              <img src="{@docRoot}images/home/auto-wordmark.png">
+              <img src="{@docRoot}images/home/auto.png">
+              <p class="landing-small">
+                Extend your music apps to automobile
+                entertainment systems.
+             </p>
+            <p class="landing-small">
+              <a href="{@docRoot}auto/index.html">Learn about Android Auto</a>
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>  <!-- end .wrap -->
+  </div> <!-- end .landing-section -->
\ No newline at end of file
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index 8a4ac47..cd49ace 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -1,4 +1,20 @@
 var RESOURCE_COLLECTIONS = {
+  "index/primary": {
+    "title": "",
+    "resources": [
+      "training/building-wearables.html",
+      "preview/material/index.html",
+      "sdk/installing/studio.html" 
+    ]
+  },
+  "index/devices": {
+    "title": "",
+    "resources": [
+      "wear/index.html",
+      "tv/index.html",
+      "auto/index.html"
+    ]
+  },
   "launch/static": {
     "title": "",
     "resources": [
@@ -29,14 +45,14 @@
     "resources": [
       "distribute/googleplay/edu/about.html",
       "distribute/googleplay/edu/start.html",
-      "distribute/googleplay/edu/faq.html"
+      "https://developers.google.com/edu/faq"
     ]
   },
   "distribute/essentials": {
     "resources": [
       "distribute/essentials/quality/core.html",
       "distribute/essentials/quality/tablets.html",
-      "distribute/essentials/gpfe-guidelines.html",
+      "https://developers.google.com/edu/guidelines",
       "distribute/essentials/optimizing-your-app.html",
       "distribute/essentials/best-practices/apps.html",
       "distribute/essentials/best-practices/games.html"
@@ -49,7 +65,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 +77,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"
@@ -147,16 +165,18 @@
     "title": "About Google Play for Education / Developers",
     "resources": [
       "distribute/googleplay/edu/start.html",
-      "distribute/essentials/gpfe-guidelines.html",
-      "distribute/googleplay/edu/faq.html",
-      "distribute/essentials/quality/tablets.html"
+      "https://developers.google.com/edu/guidelines",
+      "https://developers.google.com/edu/faq",
+      "distribute/essentials/quality/tablets.html",
+      "https://developers.google.com/edu/",
+      "https://www.google.com/edu/tablets/#tablets-family"
     ]
   },
   "distribute/googleplay/gpfe/dev": {
     "title": "About Google Play for Education / Developers",
     "resources": [
       "distribute/googleplay/edu/about.html",
-      "distribute/essentials/gpfe-guidelines.html",
+      "https://developers.google.com/edu/guidelines",
       "distribute/essentials/quality/tablets.html",
       "distribute/googleplay/developer-console.html",
       "http://play.google.com/about/developer-distribution-agreement-addendum.html",
@@ -188,7 +208,7 @@
     "resources": [
       "distribute/googleplay/developer-console.html",
       "distribute/googleplay/edu/start.html",
-      "distribute/googleplay/edu/faq.html"
+      "https://developers.google.com/edu/faq"
     ]
   },
   "distribute/essentials/eduessentials/educators": {
@@ -214,6 +234,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 +421,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": [
@@ -553,7 +588,7 @@
     "resources": [
       "distribute/essentials/quality/core.html",
       "distribute/essentials/quality/tablets.html",
-      "distribute/essentials/gpfe-guidelines.html"
+      "https://developers.google.com/edu/guidelines"
     ]
   },
   "distribute/toolsreference/launchchecklist/rating": {
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index f26b747b..605af52 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -208,7 +208,7 @@
     "keywords": [],
     "type": "support",
     "titleFriendly": ""
-  },   
+  },
   {
     "lang": "en",
     "group": "",
@@ -571,7 +571,7 @@
     "image":"http://chart.googleapis.com/chart?chl=GL%201.1%20only%7CGL%202.0%7CGL%203.0&chf=bg%2Cs%2C00000000&chd=t%3A0.1%2C93.5%2C6.4&chco=c4df9b%2C6fad0c&chs=400x250&cht=p",
     "lang":"en",
     "type":"about"
-  }, 
+  },
   {
     "lang": "en",
     "group": "",
@@ -700,7 +700,7 @@
     "timestamp": 1383243492000,
     "image": "http://i1.ytimg.com/vi/vzvpcEffvaE/maxresdefault.jpg",
     "title": "Introducing Google Play for Education",
-    "summary": "Google Play for Education is a destination where schools can find great, teacher-approved, educational apps and videos on Play Store. Teachers can filter content by subject matter, grade and other criteria. Bulk purchase and instant distribution let educators bring your apps directly to classrooms and schools.",
+    "summary": "Google Play for Education is a destination where schools can find great, teacher-approved, educational content&mdash;from videos and books, to educational apps&mdash;all in one place. Teachers can filter content by subject matter, grade and other criteria. Bulk purchase and instant distribution let educators bring your apps directly to classrooms and schools.",
     "keywords": [],
     "type": "youtube",
     "titleFriendly": ""
@@ -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,69 @@
     "type": "Google+",
     "titleFriendly": ""
   },
-]); 
\ No newline at end of file
+  {
+    "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": ""
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [],
+    "url": "https://developers.google.com/edu/guidelines",
+    "timestamp": null,
+    "image": "http://developer.android.com/distribute/images/edu-guidelines.jpg",
+    "title": "Education Guidelines",
+    "summary": "These guidelines and requirements help you develop great apps for students, which offer compelling content and an intuitive user experience on Android tablets.",
+    "keywords": [],
+    "type": "",
+    "titleFriendly": ""
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [],
+    "url": "https://developers.google.com/edu/faq",
+    "timestamp": null,
+    "image": "http://developer.android.com/distribute/images/gpfe-faq.jpg",
+    "title": "Education FAQ",
+    "summary": "Answers to common questions you might have about Google Play for Education.",
+    "keywords": [],
+    "type": "",
+    "titleFriendly": ""
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [],
+    "url": "https://developers.google.com/edu/",
+    "timestamp": null,
+    "image": "https://developers.google.com/edu/images/home-android.png",
+    "title": "Chrome Apps in Google Play for Education",
+    "summary": "Find out more about Chrome apps in Google Play for Education.",
+    "keywords": [],
+    "type": "",
+    "titleFriendly": ""
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [],
+    "url": "https://www.google.com/edu/tablets/#tablets-family",
+    "timestamp": null,
+    "image": "https://www.google.com/edu/images/tablets/big-tablet.png",
+    "title": "Google Play for Education Tablets",
+    "summary": "Google Play for Education leverages a diverse set up tablets approved for the classroom which may help inform you how to build educational apps.",
+    "keywords": [],
+    "type": "",
+    "titleFriendly": ""
+  }
+]);
diff --git a/docs/html/jd_tag_helpers.js b/docs/html/jd_tag_helpers.js
index ca01386..7538e4d 100644
--- a/docs/html/jd_tag_helpers.js
+++ b/docs/html/jd_tag_helpers.js
@@ -7,6 +7,7 @@
 }
 
 var ALL_RESOURCES = mergeArrays(
+  ABOUT_RESOURCES,
   DESIGN_RESOURCES,
   DISTRIBUTE_RESOURCES,
   GOOGLE_RESOURCES,
@@ -63,6 +64,7 @@
 // Type lookups
 
 var ALL_RESOURCES_BY_TYPE = {
+  'about': ABOUT_RESOURCES,
   'design': DESIGN_RESOURCES,
   'distribute': DISTRIBUTE_RESOURCES,
   'google': GOOGLE_RESOURCES,
@@ -78,6 +80,7 @@
 // Tag lookups
 
 var ALL_RESOURCES_BY_TAG = mergeMaps(
+  {map:ABOUT_BY_TAG,arr:ABOUT_RESOURCES},
   {map:DESIGN_BY_TAG,arr:DESIGN_RESOURCES},
   {map:DISTRIBUTE_BY_TAG,arr:DISTRIBUTE_RESOURCES},
   {map:GOOGLE_BY_TAG,arr:GOOGLE_RESOURCES},
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
new file mode 100644
index 0000000..2e99194
--- /dev/null
+++ b/docs/html/preview/api-overview.jd
@@ -0,0 +1,907 @@
+page.title=API Overview
+excludeFromSuggestions=true
+sdk.platform.apiLevel=20
+@jd:body
+
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document
+    <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle">
+        <span class="more">show more</span>
+        <span class="less" style="display:none">show less</span></a></h2>
+
+<ol id="toc44" class="hide-nested">
+  <li><a href="#Behaviors">Important Behavior Changes</a>
+    <ol>
+      <li><a href="#ART">New Android Runtime (ART)</a></li>
+      <li><a href="#BehaviorNotifications">If your app implements notifications...</a></li>
+      <li><a href="#BehaviorMediaControl">If your app uses RemoteControlClient...</a></li>
+<li><a href="#BehaviorGetRecentTasks">If your app uses ActivityManager.getRecentTasks()...</a></li>
+    </ol>
+  </li>
+  <li><a href="#UI">User Interface</a>
+    <ol>
+      <li><a href="#MaterialDesign">Material design support</a></li>
+      <li><a href="#LockscreenNotifications">Lockscreen notifications</a></li>
+      <li><a href="#NotificationsMetadata">Notifications metadata</a></li>
+      <li><a href="#Recents">Concurrent documents and activities in the Recents screen</a></li>
+      <li><a href="#WebView">WebView updates</a></li>
+    </ol>
+  </li>
+  <li><a href="#Graphics">Graphics</a>
+    <ol>
+      <li><a href="#OpenGLES-3-1">Support for OpenGL ES 3.1</a></li>
+      <li><a href="#AndroidExtensionPack">Android Extension Pack</a></li>
+    </ol>
+  </li>
+  <li><a href="#Multimedia">Multimedia</a>
+    <ol>
+      <li><a href="#Camera-v2">Camera API for advanced camera capabilities</a></li>
+      <li><a href="#AudioPlayback">Audio playback</a></li>
+      <li><a href="#MediaPlaybackControl">Media playback control</a></li>
+    </ol>
+  </li>
+  <li><a href="#Storage">Storage</a>
+    <ol>
+      <li><a href="#DirectorySelection">Directory selection</a></li>
+    </ol>
+  </li>
+  <li><a href="#Wireless">Wireless and Connectivity</a>
+    <ol>
+      <li><a href="#Multinetwork">Multiple network connections</a></li>
+      <li><a href="#BluetoothBroadcasting">Bluetooth broadcasting</a></li>
+      <li><a href="#NFCEnhancements">NFC enhancements</a></li>
+    </ol>
+  </li>
+  <li><a href="#Power">Power Efficiency</a>
+    <ol>
+      <li><a href="#JobScheduler">Scheduling Jobs</a></li>
+      <li><a href="#PowerMeasurementTools">Developer tools for power measurement</a>
+    </ol>
+  </li>
+  <li><a href="#Enterprise">Enterprise</a>
+    <ol>
+      <li><a href="#ManagedProvisioning">Managed provisioning</a></li>
+      <li><a href="#TaskLocking">Task locking</a></li>
+    </ol>
+  </li>
+  <li><a href="#Printing">Printing Framework</a>
+    <ol>
+      <li><a href="#PDFRender">Render PDF as bitmap</a></li>
+    </ol>
+  </li>
+  <li><a href="#TestingA11y">Testing &amp; Accessibility</a>
+    <ol>
+      <li><a href="#TestingA11yImprovements">Testing and accessibility improvements</a></li>
+    </ol>
+  </li>
+  <li><a href="#IME">IME</a>
+    <ol>
+      <li><a href="#Switching">Easier switching between input languages</a></li>
+    </ol>
+  </li>
+  <li><a href="#Manifest">Manifest Declarations</a>
+    <ol>
+      <li><a href="#ManifestFeatures">Declarable required features</a></li>
+    </ol>
+  </li>
+</ol>
+
+</div>
+</div>
+
+<p>The L Developer Preview gives you an advance look at the upcoming release
+for the Android platform, which offers new features for users and app
+developers. This document provides an introduction to the most notable APIs.</p>
+
+<p>The L Developer Preview is intended for <strong>developer early
+adopters</strong> and <strong>testers</strong>. If you are interested in
+influencing the direction of the Android framework,
+<a href="{@docRoot}preview/setup-sdk.html">give the L Developer Preview a
+try</a> and send us your feedback!</p>
+
+<p class="caution"><strong>Caution:</strong> Do not not publish apps
+that use the L Developer Preview to the Google Play store.</p>
+
+<p class="note"><strong>Note:</strong> This document often refers to classes and
+methods that do not yet have reference material available on <a
+href="{@docRoot}">developer.android.com</a>. These API elements are
+formatted in {@code code style} in this document (without hyperlinks). For the
+preliminary API documentation for these elements, download the <a
+href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip">preview
+reference</a>.</p>
+
+<h2 id="Behaviors">Important Behavior Changes</h2>
+
+<p>If you have previously published an app for Android, be aware that your app
+  might be affected by changes in the upcoming release.</p>
+
+<h3 id="ART">New Android Runtime (ART)</h3>
+
+<p>The 4.4 release introduced a new, experimental Android runtime, ART. Under
+4.4, ART was optional, and the default runtime remained Dalvik. With the L
+Developer Preview, ART is now the default runtime.</p>
+
+<p>For an overview of ART's new features, see
+<a href="https://source.android.com/devices/tech/dalvik/art.html">Introducing
+ART</a>. Some of the major new features are:</p>
+
+<ul>
+  <li>Ahead-of-Time (AOT) compilation</li>
+  <li>Improved garbage collection (GC)</li>
+  <li>Improved debugging support</li>
+</ul>
+
+<p>Most Android apps should just work without change under ART. However, some
+techniques that work on Dalvik do not work on ART. For information about the
+most important issues, see
+<a href="{@docRoot}guide/practices/verifying-apps-art.html">Verifying App
+Behavior on the Android Runtime (ART)</a>. Pay particular attention if:</p>
+
+<ul>
+  <li>Your app uses Java Native Interface (JNI) to run C/C++ code.</li>
+  <li>You use development tools that generate non-standard code (such as some
+      obfuscators).</li>
+  <li>You use techniques that are incompatible with compacting garbage
+      collection. (ART does not currently implement compacting GC, but
+      compacting GC is under development in the Android Open-Source
+      Project.)</li>
+</ul>
+
+<h3 id="BehaviorNotifications">If your app implements notifications...</h3>
+
+<p>Notifications are drawn with dark text atop white (or very light)
+backgrounds to match the new material design widgets. Make sure that all your
+notifications look right with the new color scheme:</p>
+
+<div class="figure" style="width:320px">
+  <img src="images/hun-example.png"
+    srcset="images/hun-example@2x.png 2x"
+    alt="" width="320" height="541" id="figure1" />
+  <p class="img-caption">
+    <strong>Figure 1.</strong> Fullscreen activity showing a heads-up notification
+  </p>
+</div>
+
+<ul>
+
+  <li>Update or remove assets that involve color.</li>
+
+  <li>The system automatically inverts action icons in notifications. Use
+  {@code android.app.Notification. Builder.setColor()} to set an accent color
+  in a circle behind your {@link android.app.Notification#icon} image.</li>
+
+  <li>The system ignores all non-alpha channels in action icons and the main
+  notification icon. You should assume that these icons are alpha-only.</li>
+
+</ul>
+
+<p>If you are currently adding sounds and vibrations to your notifications by
+using the {@link android.media.Ringtone}, {@link android.media.MediaPlayer},
+or {@link android.os.Vibrator} classes, remove this code so that
+the system can present notifications correctly in Do
+not Disturb mode. Instead, use the {@link android.app.Notification.Builder}
+methods instead to add sounds and vibration.</p>
+
+<p>Notifications now appear in a small floating window
+(also called a <em>heads-up notification</em>) when the device is active
+(that is, the device is unlocked and its screen is on). These notifications
+appear similar to the compact form of your notification, except that the
+heads-up notification also shows action buttons. Users can act on, or dismiss,
+a heads-up notification without leaving the current app.</p>
+
+<p>Examples of conditions that may trigger heads-up notifications include:</p>
+
+<ul>
+  <li>The user's activity is in fullscreen mode (the app uses
+{@link android.app.Notification#fullScreenIntent}), or</li>
+  <li>The notification has high priority and uses ringtones or
+    vibrations</li>
+</ul>
+
+<p>If your app implements notifications under those scenarios, make sure that
+heads-up notifications are presented correctly.</p>
+
+<h3 id="BehaviorMediaControl">If your app uses RemoteControlClient...</h3>
+
+<p>Lockscreens in the L Developer Preview do not show transport controls for
+your {@link android.media.RemoteControlClient}. Instead, your app can provide
+media playback control from the lockscreen through a notification. This
+gives your app more control over the presentation of media buttons, while
+providing a consistent experience for users across the lockscreen and
+unlocked device.</p>
+
+<p>The L Developer Preview introduces a new
+{@code android.app.Notification.MediaStyle} template which is recommended for
+this purpose. {@code MediaStyle} converts notification actions that you added
+with
+{@link android.app.Notification.Builder#addAction(int, java.lang.CharSequence,
+  android.app.PendingIntent)
+Notification.Builder.addAction()} into compact buttons embedded in your app's
+media playback notifications.</p>
+
+<p>If you are using the new
+{@code android.media.session.MediaSession} class
+(see <a href="#MediaPlaybackControl">Media Playback Control</a> below), attach
+your session token with {@code Notification.MediaStyle.setMediaToken()} to
+inform the system that this notification controls an ongoing media session.</p>
+
+<p>Call {@code
+Notification.Builder.setVisibility(Notification.VISIBILITY_PUBLIC)} to mark a
+notification as safe to show atop any lockscreen (secure or otherwise). For more
+information, see <a href="#LockscreenNotifications">Lockscreen Notifications</a>.</p>
+
+<h3 id="BehaviorGetRecentTasks">If your app uses ActivityManager.getRecentTasks()...</h3>
+
+<p>With the introduction of the new <em>concurrent documents and activities
+tasks</em> feature in the upcoming release (see <a href="#Recents">Concurrent
+documents and activities in Recents screen</a> below),
+the {@link android.app.ActivityManager#getRecentTasks
+ActivityManager.getRecentTasks()} method is now deprecated to improve user
+privacy. For backward compatibility, this method still returns a small subset of
+its data, including the calling application’s own tasks and possibly some other
+non-sensitive tasks (such as Home). If your app is using this method to retrieve
+its own tasks, use {@code android.app.ActivityManager.getAppTasks()} instead to
+retrieve that information.</p>
+
+<h2 id="UI">User Interface</h2>
+
+<h3 id="MaterialDesign">Material design support</h3>
+
+<p>The upcoming release adds support for Android's new <em>material</em> design
+style. You can create apps with material design that are visually dynamic and
+have UI element transitions that feel natural to users. This support includes:</p>
+
+<ul>
+
+  <li>The material theme</li>
+  <li>View shadows</li>
+  <li>The {@code RecyclerView} widget</li>
+  <li>Drawable animation and styling effects</li>
+  <li>Material design animation and activity transition effects</li>
+  <li>Animators for view properties based on the state of a view</li>
+  <li>Customizable UI widgets and app bars with color palettes that you control</li>
+</ul>
+
+<p>To learn more about adding material design functionality to your app, see
+<a href="{@docRoot}preview/material/index.html">Material Design</a>.</p>
+
+<h3 id="LockscreenNotifications">Lockscreen notifications</h3>
+<p>Lockscreens in the L Developer Preview have the ability to present
+notifications. Users can choose via <em>Settings</em> whether to allow
+sensitive notification content to be shown over a secure lockscreen.</p>
+
+<p>Your app can control the level of detail visible when its notifications are
+displayed over the secure lockscreen. To control the visibility level, call
+{@code android.app.Notification.Builder.setVisibility()} and specify one of these
+values:</p>
+
+<ul>
+<li>{@code VISIBILITY_PRIVATE}. Shows basic information, such as the
+notification’s icon, but hides the notification’s full content.</li>
+<li>{@code VISIBILITY_PUBLIC}. Shows the notification’s full content.</li>
+<li>{@code VISIBILITY_SECRET}. Shows nothing, excluding even the
+notification’s icon.</li>
+</ul>
+
+<p>When {@code VISIBILITY_PRIVATE} is set, you can also provide a redacted
+version of the notification content that hides personal details. For example,
+an SMS app might display a notification that shows "You have 3 new text messages."
+but hides the message content and senders. To provide this alternative
+notification, first create the replacement notification using
+{@link android.app.Notification.Builder}. When you create the private
+notification object, attach the replacement notification to it through the
+{@code Notification.Builder.setPublicVersion()} method.</p>
+
+<h3 id="NotificationsMetadata">Notifications metadata</h3>
+<p>The L Developer Preview uses metadata associated with your app notifications
+to sort the notifications more intelligently. To set the metadata, call the
+following methods in {@code android.app.Notification.Builder} when you
+construct the notification:</p>
+
+<ul>
+<li>{@code setCategory()}. Depending on the message category, this tells
+the system how to handle your app notifications when the device is
+in <em>Do not Disturb</em> mode (for example, if your notification represents an
+incoming call, instant message, or alarm).
+<li>{@code setPriority()}. Notifications with the priority field set to
+{@code PRIORITY_MAX} or {@code PRIORITY_HIGH} will appear in a small floating
+window if the notification also has sound or vibration.</li>
+<li>{@code addPerson()}. Allows you to add a list of people to a notification.
+Your app can use this to signal to the system that it should group together
+notifications from the specified people, or rank notifications from these
+people as being more important.</li>
+</ul>
+
+<h3 id="Recents">Concurrent documents and activities in the Recents screen</h3>
+
+<p>In previous releases, the
+<a href="{@docRoot}design/get-started/ui-overview.html">Recents screen</a>
+could only display a single task for each app that the user interacted with
+most recently. Now your app can open more tasks as
+needed for additional concurrent activities for documents.
+This feature facilitates multitasking by letting users quickly switch between
+individual activities and documents from the Recents screen, with a consistent
+switching experience across all apps.
+Examples of such concurrent tasks might include open tabs in a web
+browser app, documents in a productivity app, concurrent matches in
+a game, or chats in a messaging app. Your app can manage its tasks
+through the {@code android.app.ActivityManager.AppTask} class.</p>
+
+<p>To insert a logical break so that the system treats your activity as a new
+task, use {@code android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT} when
+launching the activity with {@link android.app.Activity#startActivity(android.content.Intent)
+startActivity()}. You can also get this behavior by declaring the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a>
+attribute {@code documentLaunchMode="intoExisting"} or {@code ="always"} in your
+manifest.</p>
+
+<p>You can also mark that a task should be removed from the Recents screen
+when all its activities are closed. To do this, use {@code
+android.content.Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} when starting the
+root activity for
+the task. You can also set this behavior for an activity by declaring the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a>
+attribute {@code autoRemoveFromRecents=“true”} in your manifest.</p>
+
+<p>To avoid cluttering the Recents screen, you can set the maximum number of
+tasks from your app that can appear in that screen. To do this, set the
+<a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a>
+attribute {@code android:maxRecent}. The current maximum that can be specified
+is 100 tasks per user.</a></p>
+
+<h3 id="WebView">WebView updates</h3>
+<p>The L Developer Preview updates the {@link android.webkit.WebView}
+implementation to Chromium M36, bringing security and stability enhancements,
+as well as bug fixes. The default user-agent string for a
+{@link android.webkit.WebView}  running on the L Developer Preview has
+been updated to incorporate 36.0.0.0 as the version number.</p>
+
+<p>Additionally, this release brings support for the
+<a href="http://webaudio.github.io/web-audio-api/" class="external-link">WebAudio</a>,
+<a href="https://www.khronos.org/webgl/" class="external-link">WebGL</a>, and
+<a href="http://www.webrtc.org/" class="external-link">WebRTC</a> open standards. To learn more about
+the new features included in this release, see <a href="https://developer.chrome.com/multidevice/webview/overview" class="external-link">WebView for Android</a>.</p>
+
+<h2 id="Graphics">Graphics</h2>
+
+<h3 id="OpenGLES-3-1">Support for OpenGL ES 3.1</h3>
+<p>The L Developer Preview adds Java interfaces and native support for OpenGL
+ES 3.1. Key new functionality provided in OpenGL ES 3.1 includes:</p>
+
+<ul>
+<li>Compute shaders
+<li>Separate shader objects
+<li>Indirect draw commands
+<li>Multisample and stencil textures
+<li>Shading language improvements
+<li>Extensions for advanced blend modes and debugging
+<li>Backward compatibility with OpenGL ES 2.0 and 3.0
+</ul>
+
+<p>The Java interface for OpenGL ES 3.1 on Android is provided with {@code GLES31}. When
+using OpenGL ES 3.1, be sure that you declare it in your manifest file with the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code &lt;uses-feature&gt;}</a>
+tag and the {@code android:glEsVversion} attribute. For example:</p>
+
+<pre>
+&lt;manifest&gt;
+    &lt;uses-feature android:glEsVersion="0x00030001" /&gt;
+    ...
+&lt;/manifest&gt;
+</pre>
+
+<p>For more information about using OpenGL ES, including how to check the
+device’s supported OpenGL ES version at runtime, see the
+<a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL ES API guide</a>.</p>
+
+<h3 id="AndroidExtensionPack">Android Extension Pack</h3>
+
+<p>In addition to OpenGL ES 3.1, this release provides an extension pack with Java interfaces and
+native support for advanced graphics functionality. These extensions are treated as a single
+package by Android. (If the {@code ANDROID_extension_pack_es31} extension is present, your app can
+assume all extensions in the package are present and enable the shading language features with
+a single {@code #extension} statement.</p>
+<p>The extension pack supports:</p>
+<ul>
+<li>Guaranteed fragment shader support for shader storage buffers, images, and
+  atomics (fragment shader support is optional in OpenGL ES 3.1.)</li>
+<li>Tessellation and geometry shaders</li>
+<li>ASTC (LDR) texture compression format</li>
+<li>Per-sample interpolation and shading</li>
+<li>Different blend modes for each color attachment in a frame buffer</li>
+</ul>
+
+<p>The Java interface for the extension pack is provided with {@code GLES31Ext}.
+In your app manifest, you can declare that support for the extension pack is
+required, with the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code &lt;uses-feature&gt;}</a>
+tag, but the precise syntax is not finalized in the L Developer Preview.</p>
+
+<h2 id="Multimedia">Multimedia</h2>
+
+<h3 id="Camera-v2">Camera API for advanced camera capabilities</h3>
+
+<p>The L Developer Preview introduces the new {@code android.hardware.camera2}
+API to facilitate fine-grain photo capture and image processing. You can now
+programmatically access the camera devices available to the system with {@code
+CameraManager.getCameraIdList()} and connect to a specific device with {@code
+CameraManager.openCamera()}. To start capturing images, create a {@code
+CameraCaptureSession} and specify the {@link android.view.Surface} objects for
+the captured images. The {@code CameraCaptureSession} can be configured to take
+single shots or multiple images in a burst.</p>
+
+<p>To be notified when new images are captured, implement the
+{@code CameraCaptureSession.CaptureListener()} interface and set it in your
+capture request. Now when the system completes the image capture request, your
+{@code CameraCaptureSession.CaptureListener()} receives a call to
+{@code onCaptureCompleted()}, providing you with the image capture metadata in a
+{@code CaptureResult}.</p>
+
+<p>To see an example of how to use the updated Camera API, refer to the {@code Camera2Basic}
+and {@code Camera2Video} implementation samples in this release.</p>
+
+<h3 id="AudioPlayback">Audio playback</h3>
+<p>This release includes the following changes to
+  {@link android.media.AudioTrack}:</p>
+<ul>
+  <li>Your app can now supply audio data in floating-point format
+({@code android.media.AudioFormat.ENCODING_PCM_FLOAT}). This permits greater
+dynamic range, more consistent precision, and greater headroom. Floating-point
+arithmetic is especially useful during intermediate calculations. Playback
+end-points use integer format for audio data, and with lower bit-depth. (In the
+L Developer Preview, portions of the internal pipeline are not yet
+floating-point.)
+  <li>Your app can now supply audio data as a {@link java.nio.ByteBuffer}, in
+the same format as provided by {@link android.media.MediaCodec}.
+  <li>The {@code WRITE_NON_BLOCKING} option can simplify buffering and
+    multithreading for some apps.
+</ul>
+
+<h3 id="MediaPlaybackControl">Media playback control</h3>
+<p>You can now build your own media controller app with the new
+{@code android.media.session.MediaController} class, which provides
+simplified transport controls APIs that replace those in
+{@link android.media.RemoteControlClient}. The {@code MediaController} class
+allows thread-safe control of playback from a non-UI process, making it easier
+to control your media playback service from your app’s user interface.
+
+<p>You can also create multiple controllers to send playback commands,
+media keys, and other events to the same ongoing
+{@code android.media.session.MediaSession}. When you add a controller, you must
+call {@code MediaSession.getSessionToken()} to request an access
+token in order for your app to interact with the session.</p>
+
+<p>You can now send transport commands such as "play", "stop", "skip", and
+"set rating" by using {@code MediaController.TransportControls}. To handle
+in-bound media transport commands from controllers attached to the session,
+override the callback methods in
+{@code MediaSession.TransportControlsCallback}.</p>
+
+<p>You can also create rich notifications that allow playback control tied to a
+media session with the new {@code android.app.Notification.MediaStyle} class. By
+using the new notification and media APIs, you will ensure that the System UI
+knows about your playback and can extract and show album art.</p>
+
+<h2 id="Storage">Storage</h2>
+
+<h3 id="DirectorySelection">Directory selection</h3>
+
+<p>The L Developer Preview extends the <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage Access Framework</a> to let users select an entire directory subtree,
+giving apps read/write access to all contained documents without requiring user
+confirmation for each item.</p>
+
+<p>To select a directory subtree, build and send an
+{@code android.intent.action.OPEN_DOCUMENT_TREE} {@link android.content.Intent}.
+The system displays all
+{@link android.provider.DocumentsProvider} instances that support subtree selection,
+letting the user browse and select a directory. The returned URI represents access to the selected
+subtree. You can then use {@code DocumentsContract.buildChildDocumentsUriUsingTree()}
+and {@code DocumentsContract.buildDocumentUriUsingTree()} along with
+{@code ContentResolver.query()} to explore the subtree.</p>
+
+<p>The new {@code DocumentsContract.createDocument()} method lets you create
+new documents or directories anywhere under the subtree. To manage
+existing documents, use {@code DocumentsContract.renameDocument()} and
+{@code DocumentsContract.deleteDocument()}. Check {@code DocumentsContract.Document.COLUMN_FLAGS}
+to verify provider support for these calls before issuing them.</p>
+
+<p>If you're implementing a {@link android.provider.DocumentsProvider} and want
+to support subtree selection, implement {@code DocumentsProvider.isChildDocument()}
+and include {@code Documents.Contract.FLAG_SUPPORTS_IS_CHILD} in your
+{@code Root.COLUMN_FLAGS}.</p>
+
+<p>The L Developer Preview also introduces new package-specific directories on
+shared storage where your app can place media files for inclusion in
+{@link android.provider.MediaStore}. The new
+{@code android.content.Context.getExternalMediaDirs()} returns paths to these
+directories on all shared storage devices. Similarly to
+{@link android.content.Context#getExternalFilesDir(java.lang.String) Context.getExternalFilesDir()},
+no additional permissions are needed by your app to access the returned paths. The
+platform periodically scans for new media in these directories, but you can also
+use {@link android.media.MediaScannerConnection} to explicitly scan for new
+content.</p>
+
+<h2 id="Wireless">Wireless &amp; Connectivity</h2>
+
+<h3 id="Multinetwork">Multiple network connections</h3>
+<p>The L Developer Preview provides new multi-networking APIs. These let your app
+dynamically scan for available networks with specific capabilities, and
+establish a connection to them. This is useful when your app requires a
+specialized network, such as an SUPL, MMS, or carrier-billing network, or if
+you want to send data using a particular type of transport protocol.</p>
+
+<p>To select and connect to a network dynamically from your app follow these
+steps:</p>
+
+<ol>
+ <li>Create a {@link android.net.ConnectivityManager}.</li>
+ <li>Create a
+  {@code android.net.NetworkRequest} to specify the network features and transport
+  type your app is interested in.</li>
+  <li>To scan for suitable networks, call
+  {@code ConnectivityManager.requestNetwork()} or
+  {@code ConnectivityManager.registerNetworkCallback()}, and pass in the
+  {@code NetworkRequest} object and an implementation of
+  {@code ConnectivityManager.NetworkCallbackListener}.</li>
+
+</ol>
+
+<p>When the system detects a suitable network, it connects to the network and
+invokes the {@code NetworkCallbackListener.onAvailable()} callback. You can use
+the {@code android.net.Network} object from the callback to get additional
+information about the network, or to direct traffic to use the selected
+network.</p>
+
+<h3 id="BluetoothBroadcasting">Bluetooth broadcasting</h3>
+<p>Android 4.3 introduced platform support for
+  <a href="{@docRoot}guide/topics/connectivity/bluetooth-le.html">Bluetooth Low Energy</a>
+(BLE) in the central role. In the L Developer Preview, an Android device can now
+act as a Bluetooth LE <em>peripheral device</em>. Apps can use this capability
+to make their presence known to
+nearby devices. For instance, you can build apps that allow a device to
+function as a pedometer or health monitor and communicate its data with another
+BLE device.</p>
+
+<p>The new {@code android.bluetooth.le} APIs enable your apps to broadcast
+advertisements, scan for responses, and form connections with nearby BLE devices.
+You must add the {@code android.permission.BLUETOOTH_ADMIN} permission in your
+manifest in order for your app to use the new advertising and scanning features.</a>
+
+<p>To begin Bluetooth LE advertising so that other devices can discover
+your app, call {@code android.bluetooth.le.BluetoothAdvertiser.startAdvisertising()}
+and pass in an implementation of the
+{@code android.bluetooth.le.AdvertiseCallback} class. The callback object
+receives a report of the success or failure of the advertising operation.</p>
+
+<p> The L Developer Preview introduces the {@code
+android.bluetooth.le.ScanFilter} class so that your app can scan for only the
+specific types of devices it is interested in. To begin scanning for Bluetooth
+LE devices, call {@code android.bluetooth.le.BluetoothLeScanner.startScan()} and
+pass in a list of filters. In the method call, you must also provide an
+implementation of {@code android.bluetooth.le.ScanCallback} to report if a
+Bluetooth LE advertisement is found. </p>
+
+<h3 id="NFCEnhancements">NFC enhancements</h3>
+<p>The L Developer Preview adds these enhancements to enable wider and more
+flexible use of NFC:</p>
+
+<ul>
+<li>Android Beam is now available in the share menu.
+<li>Your app can invoke the Android Beam on the user’s device to share data by
+calling {@code android.nfc.NfcAdapter.invokeBeam()}. This avoids the need for
+the user to manually tap the device against another NFC-capable device to
+complete the data transfer.
+<li>You can use the new {@code android.nfc.NdefRecord.createTextRecord()} method
+to create an NDEF record containing UTF-8 text data.
+<li>If you are developing a payment app, you now have the ability to
+register an NFC application ID (AID) dynamically by calling
+{@code android.nfc.cardemulation.CardEmulation.registerAidsForService()}.
+You can also use {@code android.nfc.cardemulation.CardEmulation.setPreferredService()}
+to set the preferred card emulation service that should be used when a specific
+activity is in the foreground.
+</ul>
+
+<h2 id="Power">Power Efficiency</h2>
+
+<h3 id="JobScheduler">Scheduling jobs</h3>
+<p>The L Developer Preview provides a new {@code android.app.job.JobScheduler}
+API that lets you optimize battery life by defining jobs for the system to run
+asynchronously at a later time or under specified conditions (such as when the
+device is charging). This is useful in such situations as:</p>
+<ul>
+  <li>The app has non-user-facing work that you can defer.</li>
+  <li>The app has work you'd prefer to do when the unit is plugged in.</li>
+  <li>The app has a task that requires network access (or requires a Wi-Fi
+    connection).</li>
+  <li>The app has a number of tasks that you want to run as a batch on a regular
+   schedule.</li>
+
+</ul>
+
+<p>A unit of work is encapsulated by a {@code android.app.job.JobInfo} object.
+This object provides an exact description of the criteria to be used for
+scheduling.</p>
+
+<p>Use the {@code android.app.job.JobInfo.Builder} to configure how the
+scheduled task should run. You can schedule the task to run under specific
+conditions, such as:</p>
+
+<ul>
+  <li>The device is charging</li>
+  <li>The device is connected to an unmetered network</li>
+  <li>The system deems the device to be idle</li>
+  <li>Completion with a minimum delay or within a specific deadline.</li>
+</ul>
+
+<p>For example, you can add code like this to run your task on an
+unmetered network:</p>
+
+<pre>
+JobInfo uploadTask = new JobInfo.Builder(mJobId, mServiceComponent)
+        .setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
+        .build();
+
+JobScheduler jobScheduler =
+        (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE)
+jobScheduler.schedule(uploadTask);
+</pre>
+
+<p>To see an example of how to use the {@code JobScheduler} API, refer to the
+{@code JobSchedulerSample} implementation sample in this release.</p>
+
+<h3 id="PowerMeasurementTools">Developer tools for power measurement</h3>
+<p>The L Developer Preview provides several new developer tools and APIs to help
+you better measure and understand your app's power usage.</p>
+
+<dl>
+<dt><strong>batterystats</strong></dt>
+<dd>
+<p>The {@code dumpsys batterystats} command allows you to generate interesting
+statistical data about battery usage on a device, organized by unique user ID
+(UID). The statistics generated by the tool include:</p>
+
+<ul>
+<li>History of battery related events
+<li>Global statistics for the device
+<li>Approximated power use per UID and system component
+<li>Per-app mobile ms per packet
+<li>System UID aggregated statistics
+<li>App UID aggregated statistics
+</ul>
+
+<p>Use the {@code --help} option to learn about the various options for
+tailoring the output. For example, to print battery usage
+statistics for a given app package since the device was last charged, run this
+command:
+<pre>
+$ adb shell dumpsys batterystats --charged &lt;package-name&gt;
+</pre>
+</dd>
+
+<dt><strong>Battery Historian</strong></dt>
+<dd>
+<p>The Battery Historian tool ({@code historian.par}) analyzes Android
+bug reports from the L Developer Preview and creates an HTML visualization of
+power-related events. It can
+also visualize power consumption data from a power monitor, and attempts to
+map power usage to the wake locks seen. You can find the Battery Historian tool
+in {@code &lt;sdk&gt;/tools}.</p>
+
+<img src="images/battery_historian.png"
+     srcset="images/battery_historian@2x.png 2x"
+    alt="" width="760" height="462"
+    id="figure2" />
+<p class="img-caption">
+  <strong>Figure 2.</strong>HTML visualization generated by the Battery
+      Historian tool.
+</p>
+
+<p>For best results, you should first enable full wake lock reporting, to allow
+the Battery Historian tool to monitor uninterrupted over an extended period of
+time:</p>
+<pre>
+$ adb shell dumpsys batterystats --enable full-wake-history
+</pre>
+
+<p>You should also reset battery statistics at the beginning of a
+measurement:</p>
+<pre>
+$ adb shell dumpsys batterystats --reset
+</pre>
+
+<p>To generate an HTML visualization:</p>
+<pre>
+$ historian.par [-p powerfile] bugreport.txt > out.html
+</pre>
+</dd>
+
+</dl>
+
+<h2 id="Enterprise">Enterprise</h2>
+<h3 id="ManagedProvisioning">Managed provisioning</h3>
+
+<div class="figure" style="width:360px">
+  <img src="images/managed_apps_launcher.png"
+    srcset="images/managed_apps_launcher@2x.png 2x"
+    alt="" width="360" height="609" id="figure3" />
+  <p class="img-caption">
+    <strong>Figure 3.</strong> Launcher screen showing managed apps (marked with
+    a lock badge)
+  </p>
+</div>
+
+<p>The L Developer Preview provides new functionality for running apps within
+an enterprise environment. A
+<a href="{@docRoot}guide/topics/admin/device-admin.html">device administrator</a> can
+initiate a managed provisioning process to add a co-present but separate <em>managed profile</em> to a device, if the user has an existing personal account.
+Apps that are associated with managed profiles will appear alongside
+non-managed apps in the user’s Launcher, Recent apps screen, and notifications.</p>
+
+<p>To start the managed provisioning process, send {@code
+ACTION_PROVISION_MANAGED_PROFILE} in an {@link android.content.Intent}. If the
+call is successful, the system triggers the {@code
+android.app.admin.DeviceAdminReceiver. onProfileProvisioningComplete()} callback.
+You can then call {@code app.admin.DevicePolicyManager. setProfileEnabled()} to
+enable this managed profile.</p>
+
+<p>If you are developing a Launcher app, you can use the new {@code
+android.content.pm.LauncherApps} class to get a list of launchable activities
+for the current user and any associated managed profiles. Your Launcher can make
+the managed apps visually prominent by appending a “work” badge to the icon
+drawable with {@code android.os.UserManager. getBadgeDrawableForUser()}.</p>
+
+<p>To see an example of how to use the new functionality, refer to the
+{@code BasicManagedProfile} implementation sample in this release.</p>
+
+<h3 id="TaskLocking">Task locking</h3>
+<p>The L Developer Preview introduces a new task locking API that
+lets you temporarily restrict users from leaving your app or being interrupted
+by notifications. This could be used, for example, if you are developing an
+education app to support high stakes assessment requirements on Android.
+Once your app activates this mode, users will not be able to see
+notifications, access other apps, or return to the Home screen, until your
+app exits the mode.</p>
+
+<p>To prevent unauthorized usage, only authorized apps can activate task locking.
+Furthermore, task locking authorization must be granted by a
+specially-configured <em>device owner</em> app, through the {@code android.app.admin.DevicePolicyManager.setLockTaskComponents()} method.</p>
+
+<p>To set up a device owner, follow these steps:</p>
+<ol>
+<li>Attach a device running an Android <a href="https://source.android.com/source/building-running.html" class="external-link">{@code userdebug}</a> build to your development machine.</li>
+<li>Install your device owner app.</li>
+<li>Create a {@code device_owner.xml} file and save it to the {@code /data/system}
+directory on the device.
+<pre>
+$ adb root
+$ adb shell stop
+$ rm /tmp/device_owner.xml
+$ echo "&lt;?xml version='1.0' encoding='utf-8' standalone='yes' ?&gt;"
+&gt;&gt; /tmp/device_owner.xml
+$ echo "&device-owner package=\"&lt;your_device_owner_package&gt;\"
+name=\"*&lt;your_organization_name&gt;\" /&gt;" &gt;&gt; /tmp/device_owner.xml
+$ adb push /tmp/device_owner.xml /data/system/device_owner.xml
+$ adb reboot
+</pre>
+</li>
+</ol>
+
+<p>Before using the task locking API in your app, verify that your activity is
+authorized by calling {@code DevicePolicyManager.isLockTaskPermitted()}.</p>
+
+<p>To activate task locking, call
+{@code android.app.Activity.startLockTask()} from your authorized activity.</p>
+
+<p>When task locking is active, the following behavior takes effect:</p>
+
+<ul>
+<li>The status bar is blank, and user notifications and status information is
+hidden.</li>
+<li>The Home and Recent Apps buttons are hidden.</li>
+<li>Other apps may not launch new activities.</li>
+<li>The current app may start new activities, as long as doing so does not
+create new tasks.</li>
+<li>The user remains locked on your app until an authorized activity calls
+{@code Activity.stopLockTask()}.</li>
+</ul>
+
+<h2 id="Printing">Printing Framework</h2>
+
+<h3 id="PDFRender">Render PDF as bitmap</h3>
+<p>You can now render PDF document pages into bitmap images for printing by
+using the new {@code android.graphics.pdf.PdfRenderer} class. You must specify a
+{@link android.os.ParcelFileDescriptor} that is seekable (that is, the content
+can be randomly accessed) on which the system writes the the printable content.
+Your app can obtain a page for rendering with {@code openPage()}, then call
+{@code render()} to turn the opened {@code PdfRenderer.Page} into a bitmap. You
+can also set additional parameters if you only want to convert a portion of the
+document into a bitmap image (for example, to implement
+<a href="http://en.wikipedia.org/wiki/Tiled_rendering" class="external-link">tiled rendering</a> in
+order to zoom in on the document).</p>
+
+<h2 id="TestingA11y">Testing &amp; Accessibility </h2>
+
+<h3 id="TestingA11yImprovements">Testing and accessibility improvements</h3>
+<p>The L Developer Preview adds the following support for testing and
+accessibility:</p>
+
+<ul>
+<li>You can use the new {@code android.app.UiAutomation.getWindowAnimationFrameStats()}
+and {@code android.app.UiAutomation.getWindowContentFrameStats()} methods to
+capture frame statistics for window animations and content. This lets you
+write instrumentation tests to evaluate if the app under test is rendering
+frames at a sufficient refresh frequency to provide a smooth user experience.
+
+<li>You can execute shell commands from your instrumentation test with the new
+{@code android.app.UiAutomation.executeShellCommand()}. The command execution
+is similar to running {@code adb shell} from a host connected to the device. This
+allows you to use shell based tools such as {@code dumpsys}, {@code am},
+{@code content}, and {@code pm}.
+
+<li>Accessibility services and test tools that use the accessibility APIs
+(such as <a href="{@docRoot}tools/help/uiautomator/index.html">uiautomator</a>)
+can now retrieve detailed information about the properties of windows on the
+screen that sighted users can interact with. To retrieve a list of
+{@code android.view.accessibility.AccessibilityWindowInfo} objects
+representing the windows information, call the new
+{@code android.accessibilityservice.AccessibilityService.getWindows()} method.
+<li>You can use the new {@code android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction} to define standard or customized
+actions to perform on an {@link android.view.accessibility.AccessibilityNodeInfo}.
+The new {@code AccessibilityAction} class replaces the actions-related APIs
+previously found in {@code AccessibilityNodeInfo}.
+</ul>
+
+<h2 id="IME">IME</h2>
+
+<h3 id="Switching">Easier switching between input languages</h3>
+
+<p>Beginning in the L Developer Preview, users can more easily switch between
+all <a href="{@docRoot}guide/topics/text/creating-input-method.html">input
+method editors (IME)</a> supported by the platform. Performing the designated
+switching action (usually touching a Globe icon on the soft keyboard) will cycle
+among all such IMEs. This change takes place in
+{@link android.view.inputmethod.InputMethodManager#shouldOfferSwitchingToNextInputMethod
+InputMethodManager.shouldOfferSwitchingToNextInputMethod()}.</p>
+
+<p>In addition, the framework now checks whether the next IME includes a
+switching mechanism at all (and, thus, whether that IME supports switching to
+the IME after it). An
+IME with a switching mechanism will not cycle to an IME without one. This
+change takes place in
+{@link android.view.inputmethod.InputMethodManager#switchToNextInputMethod
+InputMethodManager.switchToNextInputMethod}.
+
+<p>To see an example of how to use the updated IME-switching APIs, refer to the
+updated soft-keyboard implementation sample in this release.</p>
+
+<h2 id="Manifest">Manifest Declarations</h2>
+
+<h3 id="ManifestFeatures">Declarable required features</h3>
+<p>The following values are now supported in the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code &lt;uses-feature&gt;}</a>
+element, so you can ensure that your app is installed only on devices that provide the features
+your app needs.</p>
+
+<ul>
+<li>{@code FEATURE_LEANBACK}. Declares that your app must be installed only on
+devices that support the
+<a href="{@docRoot}training/tv/index.html">Android TV</a> user interface.
+Example:
+<pre>
+&lt;uses-feature android:name="android.software.leanback"
+              android:required="true" /&gt;
+</pre>
+
+<li>{@code FEATURE_WEBVIEW}. Declares that your app must only be installed on
+devices that fully implement the {@code android.webkit.*} APIs. Example:
+<pre>
+&lt;uses-feature android:name="android.software.webview"
+              android:required="true" /&gt;
+</pre>
+</ul>
+
+<p class="note">For a detailed view of all API changes in the L Developer Preview, see the
+<a href="{@docRoot}preview/reference.html">API Differences Report</a>.</p>
diff --git a/docs/html/preview/google-play-services-wear.html b/docs/html/preview/google-play-services-wear.html
new file mode 100644
index 0000000..84647b7
--- /dev/null
+++ b/docs/html/preview/google-play-services-wear.html
@@ -0,0 +1,118 @@
+<html><head>
+
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<meta name="viewport" content="width=device-width">
+
+<meta name="Description" content="Google Play services will fully roll out to the hundreds of millions of Android devices in early July. Because of this, we usually wait to release the Google Play services SDK until all users receive the most updated Google Play services on their devices…">
+<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
+<title>Google Play services Preview for Wear | Android Developers</title>
+
+<!-- STYLESHEETS -->
+<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold" title="roboto">
+<link href="/assets/css/default.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+<div class="col-12" id="doc-col">
+
+<h1 itemprop="name">Google Play services Preview for Wear</h1>
+
+<div id="jd-content">
+<div class="jd-descr" itemprop="articleBody">
+<p>Google Play services 5.0  is currently being rolled out to the world.
+We usually wait to release the Google Play services SDK until the rollout completes.
+This ensures that your newly-updated apps can run on the most devices as possible.</p>
+
+<p>However, if you want to develop for Android Wear now, complete the following steps to get
+special access to all the things you need to start developing, without waiting for the rollout to
+complete.</p>
+
+<p class="warning"><b>Warning</b>: Do not publish any apps that are built with Google Play services
+SDK 5.0 until the rollout is completed over the coming days.  Your apps will break on most user
+devices, which will degrade your apps’ user experience. We’ll publish an update to the
+<a href="http://android-developers.blogspot.com/">Android Developer blog</a> when the rollout is done.
+</p>
+
+<h2 style="margin-bottom: 0px;">1. Get Whitelisted for the Preview</h2><hr>
+
+<p>If you attended Google I/O, your registered Google account is automatically whitelisted for
+these preview resources. You're done! Head to Step 2.</p>
+
+<p><strong><em>If you didn't attend Google I/O</em></strong> or want to use a different account,
+click following link to  and get whitelisted.</p>
+
+<a style="font-size:24px" href="https://groups.google.com/group/io14androidweardev/subscribe" target="_blank">
+Join the Preview Group</a>
+
+<h2 style="margin-bottom: 0px;">2. Download Required Apps</h2><hr>
+<p>You'll need the following apps to get the most out of Android Wear. You must be whitelisted
+in the Preview Group above and you must install these apps in this exact order:</p>
+
+<ol>
+<li><a href="https://play.google.com/apps/testing/com.google.android.gms">Google Play services</a>:
+Allows your Android Wear device to communicate with your handheld device. This is required to
+use the Android Wear app and other apps listed below.</li>
+  <li><a href="https://play.google.com/apps/testing/com.google.android.googlequicksearchbox">Google
+  Search</a>: Enables searches from Android Wear</li>
+  <li><a href="https://play.google.com/apps/testing/com.google.android.wearable.app">Android Wear
+  Companion</a>: The app for pairing a handheld to a wearable and providing syncing of
+  notifications and data
+  </li>
+  <p class="note"><b>Note:</b> After becoming a tester, it can take up to 1 hour to get access to
+  the preview versions of these apps.</p>
+ </ol>
+
+<p>To obtain these apps from Google Play, click each app link above and follow these instructions,
+preferably from your mobile browser:</p>
+
+<ol>
+ <li>Click the <b>Become a Tester</b> button to opt-in to the preview version of the app. The page
+ confirms that you're a tester after clicking.</li>
+ <li>Click the <b>Download &lt;app name&gt; from the Play Store</b> link to go to Google Play
+ Store download page to get the app. The
+ following screenshot shows how the opt-in process looks like:
+<img style="margin-top:40px" src="/preview/images/opt-in.png" /></li>
+</ol>
+
+<h2>3. Download Optional Apps</h2>
+<p>Please join the Test Group and install the following apps to enhance your Android Wear experience:
+</p>
+
+<ol>
+  <li><a href="https://play.google.com/apps/testing/com.google.android.keep">Google Keep</a>:
+  Supports the "Take a note" command</li>
+  <li><a href="https://play.google.com/apps/testing/com.google.samples.apps.iosched">Google I/O
+  2014</a>:
+  Supports session feedback from Android Wear</li>
+  </ol>
+
+<h2 style="margin-bottom: 0px;">4. Start Building</h2><hr>
+
+<p>The Google Play services SDK is required if you want to sync and send data between wearable
+and handheld devices. To get the new SDK that is compatible with the Google Play services
+APK that you just installed, follow these steps: </p>
+
+<p class="note"><b>Note:</b> Android Studio is required for Wear development.</p>
+<ol>
+  <li><a href="/sdk/installing/studio.html">Download and install Android Studio</a></li>
+  <li>Start SDK Manager.</li>
+  <li>Update the Android SDK Tools and Platform-tools to versions 23 and 20 respectively.</li>
+  <li>Click <b>Tools > Manage Add-on Sites > User Defined Sites</b>.</li>
+  <li>Click <b>New</b>, enter
+  <code>https://dl-ssl.google.com/android/repository/addon-play-services-5.xml</code> into the
+  text field, and click <b>OK</b>.</li>
+  <li>Click Close. You should now see new emulator images that support this preview
+  release of Google Play services and the Google Play services client libraries you need to
+  start developing.</li>
+  <li><a href="http://storage.googleapis.com/androiddevelopers/preview/google-play-services-preview.zip">Download</a> the Google Play
+  services reference documentation for this preview release.</li>.
+</ol>
+
+<p>When you're done here, check out the <a href="/training/building-wearables.html">Building Apps for Wearables</a>
+training classes for information on how to build for Wear.</p>
+
+</div> <!-- end jd-content -->
+</div><!-- end doc-content -->
+</div> <!-- end body-content -->
+</body>
+</html>
\ No newline at end of file
diff --git a/docs/html/preview/images/ActivitySceneTransitionBasic.png b/docs/html/preview/images/ActivitySceneTransitionBasic.png
new file mode 100644
index 0000000..ea58641
--- /dev/null
+++ b/docs/html/preview/images/ActivitySceneTransitionBasic.png
Binary files differ
diff --git a/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png b/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png
new file mode 100644
index 0000000..cd28ade
--- /dev/null
+++ b/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png
Binary files differ
diff --git a/docs/html/preview/images/BasicManagedProfile.png b/docs/html/preview/images/BasicManagedProfile.png
new file mode 100644
index 0000000..7354842
--- /dev/null
+++ b/docs/html/preview/images/BasicManagedProfile.png
Binary files differ
diff --git a/docs/html/preview/images/BasicManagedProfile@2x.png b/docs/html/preview/images/BasicManagedProfile@2x.png
new file mode 100644
index 0000000..c232809
--- /dev/null
+++ b/docs/html/preview/images/BasicManagedProfile@2x.png
Binary files differ
diff --git a/docs/html/preview/images/JobSchedulerSample.png b/docs/html/preview/images/JobSchedulerSample.png
new file mode 100644
index 0000000..ee57bdb
--- /dev/null
+++ b/docs/html/preview/images/JobSchedulerSample.png
Binary files differ
diff --git a/docs/html/preview/images/JobSchedulerSample@2x.png b/docs/html/preview/images/JobSchedulerSample@2x.png
new file mode 100644
index 0000000..3d543db
--- /dev/null
+++ b/docs/html/preview/images/JobSchedulerSample@2x.png
Binary files differ
diff --git a/docs/html/preview/images/art.png b/docs/html/preview/images/art.png
new file mode 100644
index 0000000..c48f039
--- /dev/null
+++ b/docs/html/preview/images/art.png
Binary files differ
diff --git a/docs/html/preview/images/battery_historian.png b/docs/html/preview/images/battery_historian.png
new file mode 100644
index 0000000..f1d4e40
--- /dev/null
+++ b/docs/html/preview/images/battery_historian.png
Binary files differ
diff --git a/docs/html/preview/images/battery_historian@2x.png b/docs/html/preview/images/battery_historian@2x.png
new file mode 100644
index 0000000..8c8a87f
--- /dev/null
+++ b/docs/html/preview/images/battery_historian@2x.png
Binary files differ
diff --git a/docs/html/preview/images/bugs.png b/docs/html/preview/images/bugs.png
new file mode 100644
index 0000000..46adf05
--- /dev/null
+++ b/docs/html/preview/images/bugs.png
Binary files differ
diff --git a/docs/html/preview/images/hun-example.png b/docs/html/preview/images/hun-example.png
new file mode 100644
index 0000000..251b938
--- /dev/null
+++ b/docs/html/preview/images/hun-example.png
Binary files differ
diff --git a/docs/html/preview/images/hun-example@2x.png b/docs/html/preview/images/hun-example@2x.png
new file mode 100644
index 0000000..5b98a361
--- /dev/null
+++ b/docs/html/preview/images/hun-example@2x.png
Binary files differ
diff --git a/docs/html/preview/images/l-dev-prev.png b/docs/html/preview/images/l-dev-prev.png
new file mode 100644
index 0000000..eae6ede
--- /dev/null
+++ b/docs/html/preview/images/l-dev-prev.png
Binary files differ
diff --git a/docs/html/preview/images/managed_apps_launcher.png b/docs/html/preview/images/managed_apps_launcher.png
new file mode 100644
index 0000000..b5ef407
--- /dev/null
+++ b/docs/html/preview/images/managed_apps_launcher.png
Binary files differ
diff --git a/docs/html/preview/images/managed_apps_launcher@2x.png b/docs/html/preview/images/managed_apps_launcher@2x.png
new file mode 100644
index 0000000..90d7d51
--- /dev/null
+++ b/docs/html/preview/images/managed_apps_launcher@2x.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Action.png b/docs/html/preview/images/notifications/Action.png
new file mode 100644
index 0000000..de584a3
--- /dev/null
+++ b/docs/html/preview/images/notifications/Action.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/AntiSample1.png b/docs/html/preview/images/notifications/AntiSample1.png
new file mode 100644
index 0000000..2cb5411
--- /dev/null
+++ b/docs/html/preview/images/notifications/AntiSample1.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/AntiSample3.png b/docs/html/preview/images/notifications/AntiSample3.png
new file mode 100644
index 0000000..5780583
--- /dev/null
+++ b/docs/html/preview/images/notifications/AntiSample3.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Basic.png b/docs/html/preview/images/notifications/Basic.png
new file mode 100644
index 0000000..4ce21c48
--- /dev/null
+++ b/docs/html/preview/images/notifications/Basic.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Expanded.png b/docs/html/preview/images/notifications/Expanded.png
new file mode 100644
index 0000000..9ca260b
--- /dev/null
+++ b/docs/html/preview/images/notifications/Expanded.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ExpandedImage.png b/docs/html/preview/images/notifications/ExpandedImage.png
new file mode 100644
index 0000000..7e58881
--- /dev/null
+++ b/docs/html/preview/images/notifications/ExpandedImage.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ExpandedText.png b/docs/html/preview/images/notifications/ExpandedText.png
new file mode 100644
index 0000000..0361695
--- /dev/null
+++ b/docs/html/preview/images/notifications/ExpandedText.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Headsup.png b/docs/html/preview/images/notifications/Headsup.png
new file mode 100644
index 0000000..03015b1
--- /dev/null
+++ b/docs/html/preview/images/notifications/Headsup.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Largelogo.png b/docs/html/preview/images/notifications/Largelogo.png
new file mode 100644
index 0000000..e222422
--- /dev/null
+++ b/docs/html/preview/images/notifications/Largelogo.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/LockScreen.png b/docs/html/preview/images/notifications/LockScreen.png
new file mode 100644
index 0000000..c204a81
--- /dev/null
+++ b/docs/html/preview/images/notifications/LockScreen.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/MusicPlayback.png b/docs/html/preview/images/notifications/MusicPlayback.png
new file mode 100644
index 0000000..58f3d0b
--- /dev/null
+++ b/docs/html/preview/images/notifications/MusicPlayback.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ProductIcons.png b/docs/html/preview/images/notifications/ProductIcons.png
new file mode 100644
index 0000000..227c8ab
--- /dev/null
+++ b/docs/html/preview/images/notifications/ProductIcons.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Productlogos.png b/docs/html/preview/images/notifications/Productlogos.png
new file mode 100644
index 0000000..baed5ac
--- /dev/null
+++ b/docs/html/preview/images/notifications/Productlogos.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ReplyAction.png b/docs/html/preview/images/notifications/ReplyAction.png
new file mode 100644
index 0000000..73c9fc6
--- /dev/null
+++ b/docs/html/preview/images/notifications/ReplyAction.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Stack.png b/docs/html/preview/images/notifications/Stack.png
new file mode 100644
index 0000000..90c6c4a
--- /dev/null
+++ b/docs/html/preview/images/notifications/Stack.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summarise_Do.png b/docs/html/preview/images/notifications/Summarise_Do.png
new file mode 100644
index 0000000..4daaa4d
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summarise_Do.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summarise_Dont.png b/docs/html/preview/images/notifications/Summarise_Dont.png
new file mode 100644
index 0000000..4094d73
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summarise_Dont.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summary.png b/docs/html/preview/images/notifications/Summary.png
new file mode 100644
index 0000000..135de83
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summary.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summary_Do.png b/docs/html/preview/images/notifications/Summary_Do.png
new file mode 100644
index 0000000..c48bfd3
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summary_Do.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/TimeSensitive.png b/docs/html/preview/images/notifications/TimeSensitive.png
new file mode 100644
index 0000000..55a9e59
--- /dev/null
+++ b/docs/html/preview/images/notifications/TimeSensitive.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Triggered.png b/docs/html/preview/images/notifications/Triggered.png
new file mode 100644
index 0000000..c327ed2
--- /dev/null
+++ b/docs/html/preview/images/notifications/Triggered.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/WearBasic.png b/docs/html/preview/images/notifications/WearBasic.png
new file mode 100644
index 0000000..881f1e5
--- /dev/null
+++ b/docs/html/preview/images/notifications/WearBasic.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/notifications_pattern_priority.png b/docs/html/preview/images/notifications/notifications_pattern_priority.png
new file mode 100644
index 0000000..af2d725
--- /dev/null
+++ b/docs/html/preview/images/notifications/notifications_pattern_priority.png
Binary files differ
diff --git a/docs/html/preview/images/opt-in.png b/docs/html/preview/images/opt-in.png
new file mode 100644
index 0000000..7151253
--- /dev/null
+++ b/docs/html/preview/images/opt-in.png
Binary files differ
diff --git a/docs/html/preview/images/updates.png b/docs/html/preview/images/updates.png
new file mode 100644
index 0000000..f165c5a
--- /dev/null
+++ b/docs/html/preview/images/updates.png
Binary files differ
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
new file mode 100644
index 0000000..4c42d99
--- /dev/null
+++ b/docs/html/preview/index.html
@@ -0,0 +1,361 @@
+<!DOCTYPE html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<html>
+<head>
+
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="viewport" content="width=970" />
+
+<meta name="Description" content="Test and build your apps against the next version of Android to ensure they're ready when the platform officially launches.">
+<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
+<title>Android L Developer Preview | Android Developers</title>
+
+<!-- STYLESHEETS -->
+<link rel="stylesheet"
+href="//fonts.googleapis.com/css?family=Roboto+Condensed">
+<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold"
+  title="roboto">
+<link href="/assets/css/default.css" rel="stylesheet" type="text/css">
+
+
+
+<!-- JAVASCRIPT -->
+<script src="//www.google.com/jsapi" type="text/javascript"></script>
+<script src="/assets/js/android_3p-bundle.js" type="text/javascript"></script>
+<script type="text/javascript">
+  var toRoot = "/";
+  var metaTags = [];
+  var devsite = false;
+</script>
+<script src="/assets/js/docs.js" type="text/javascript"></script>
+
+<script>
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+  ga('create', 'UA-5831155-1', 'android.com');
+  ga('create', 'UA-49880327-2', 'android.com', {'name': 'universal'});  // New tracker);
+  ga('send', 'pageview');
+  ga('universal.send', 'pageview'); // Send page view for new tracker.
+</script>
+
+</head>
+
+<body class="gc-documentation 
+
+" itemscope itemtype="http://schema.org/Article">
+
+  
+<a name="top"></a>
+<div id="body-content">
+<div class="fullpage" >
+<div id="jd-content">
+  <div class="jd-descr" itemprop="articleBody">
+    <style>
+.fullpage>#footer,
+#jd-content>.content-footer.wrap {
+  display:none;
+}
+</style>
+
+<style>
+#footer {
+    display: none;
+}
+.content-footer {
+  display: none;
+}
+</style>
+
+    <div class="landing-rest-of-page">
+      <div class="landing-section" style="padding-top:30px">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">Android L Developer Preview</div>
+            <div class="landing-subhead">
+              Get an early look at the next release and  get your apps ready when the
+              platform officially launches.
+            </div>
+
+            <img src="/preview/images/l-dev-prev.png" style=" margin:0px 0 0 40px" width="860px"/>
+            <div class="col-6" style="margin-left:660px; margin-top:-105px">
+   <a href="/preview/setup-sdk.html" class="landing-button landing-primary" style="position:absolute;z-index:100;float:right;margin-top: 0px;">Get Started</a><!--
+            <p>Set up your environment and check out all the docs to get up and running.</p>-->
+             
+         
+            </div>
+          </div>
+        </div> <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+
+
+<div class="landing-section landing-gray-background" style="margin-top:-135px; padding-bottom:20px">
+        <div class="wrap">
+          <div class="cols">
+<div class="landing-body" style="margin-top:-80px" >
+
+            <div class="landing-breakout cols">
+              <div class="col-4">
+                <p>A New UI Design</p>
+                <p class="landing-small">
+                  Create a consistent experience across mobile and the web with
+                   <b>material design</b>, the new Google-wide standard.
+                </p>
+                <p class="landing-small">
+                  <a href="/preview/material/index.html">Learn about material</a>
+                </p>
+              </div>
+              <div class="col-4">
+                <p>A New Runtime</p>
+                <p class="landing-small">
+                  Test your apps and get them ready for <b>ART</b> (<b>A</b>ndroid <b>R</b>un<b>t</b>ime),
+                  the default runtime in the next release.
+                </p>
+                <p class="landing-small">
+                  <a href="/preview/api-overview.html#ART">Learn about ART</a>
+                </p>
+              </div>
+              <div class="col-4">
+                <p style="width:230px">Enhanced Notifications</p>
+                <p class="landing-small">
+                   Get control over where notifications appear,
+                   how they look, and how they sync to non-handheld devices.
+                </p>
+                <p class="landing-small">
+                  <a href="/preview/api-overview.html#UI">Learn about notifications</a>
+                </p>
+              </div>
+              <div class="col-4">
+                <p>Increased Efficiency</p>
+                <p class="landing-small">
+                  <b>Project Volta</b> is our effort to make the platform energy efficient and
+                  to give you more control over resource usage.
+                </p>
+                <p class="landing-small">
+                  <a href="/preview/api-overview.html#Power">Learn about Project Volta</a>
+                </p>
+              </div>
+            </div>
+               <p style="margin-left:20px">See the <a href="/preview/api-overview.html">API overview</a> for more information
+              on the rest of the new and updated features.</p>
+          </div>
+          </div></div></div>
+    <div class="landing-section">
+        <div class="wrap">
+          <div class="cols">
+            <div class="landing-body">
+              <div class="col-3-wide">
+                  <a target="_blank" href="https://code.google.com/p/android-developer-preview/">
+                    <img class="landing-social-image" src="/preview/images/bugs.png" alt="">
+                  </a>
+                <div class="landing-social-copy">
+                  <p>Issue Tracker</p>
+                  <p class="landing-small">
+                  Let us know when you encounter problems, so we can fix them and make
+                  the platform better for you and your users.
+                    </p><p class="landing-small">
+                      <a href="https://code.google.com/p/android-developer-preview/">
+                      Report Issues</a>
+                    </p>
+                  <p></p>
+                </div>
+              </div>
+              <div class="col-3-wide">
+                <a target="_blank" href="http://plus.google.com">
+                  <img class="landing-social-image" src="//www.google.com/images/icons/product/gplus-128.png" alt="">
+                </a>
+                <div class="landing-social-copy">
+                  <p>Google+ </p>
+                  <p class="landing-small">
+                    Join the community of Android developers testing out the L Developer Preview and
+                    share your thoughts and experiences.
+                  </p><p class="landing-small">
+                    <a href="http://g.co/androidldevpreview">
+                    Discuss on Google+</a>
+                    </p>
+                </div>
+              </div>
+              <div class="col-3-wide">
+                <a target="_blank" href="/preview/support.html">
+                  <img class="landing-social-image" src="/preview/images/updates.png" alt="">
+                </a>
+                <div class="landing-social-copy">
+                  <p>Support and Updates</p>
+                  <p class="landing-small">
+                  Updates to the L Developer Preview are delivered
+                  in the Android SDK Manager. Check back periodically
+                  for news about the changes.
+                  </p>
+                  <p class="landing-small">
+                    <a href="/preview/support.html">Get Support</a>
+                  </p>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+    <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement">
+      <div class="layout-content-col col-16" style="padding-top:4px">
+        <style>#___plusone_0 {float:right !important;}</style>
+        <div class="g-plusone" data-size="medium"></div>
+      </div>
+    </div>
+    <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1">
+      <div id="copyright">
+        Except as noted, this content is
+        licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+        Creative Commons Attribution 2.5</a>. For details and
+        restrictions, see the <a href="/license.html">Content
+        License</a>.
+      </div>
+    </div>
+  </div> <!-- end landing-body-content -->
+
+  <script>
+  $("a.landing-down-arrow").on("click", function(e) {
+    $("body").animate({
+      scrollTop: $(".preview-hero").height() + 76
+    }, 1000, "easeOutQuint");
+    e.preventDefault();
+  });
+  </script>
+    </div>
+
+      <div class="content-footer wrap"
+                    itemscope itemtype="http://schema.org/SiteNavigationElement">
+        
+        <div class="paging-links layout-content-col col-10">
+          
+        </div>
+        <div class="layout-content-col plus-container col-2" >
+          <style>#___plusone_0 {float:right !important;}</style>
+            <div class="g-plusone" data-size="medium"></div>
+          
+        </div>
+        
+      </div>
+
+      
+      
+
+  </div> <!-- end jd-content -->
+
+<div id="footer" class="wrap" style="width:940px">
+        
+
+  <div id="copyright">
+    
+  Except as noted, this content is 
+  licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+  Creative Commons Attribution 2.5</a>. For details and 
+  restrictions, see the <a href="/license.html">Content 
+  License</a>.
+  </div>
+
+
+</div> <!-- end footer -->
+</div><!-- end doc-content -->
+
+</div> <!-- end body-content --> 
+
+
+
+
+
+  <script src="https://developer.android.com/ytblogger_lists_unified.js" type="text/javascript"></script>
+  <script src="/jd_lists_unified.js" type="text/javascript"></script>
+  <script src="/jd_extras.js" type="text/javascript"></script>
+  <script src="/jd_collections.js" type="text/javascript"></script>
+  <script src="/jd_tag_helpers.js" type="text/javascript"></script>
+
+</body>
+</html>
diff --git a/docs/html/preview/license.jd b/docs/html/preview/license.jd
new file mode 100644
index 0000000..5ff52ba
--- /dev/null
+++ b/docs/html/preview/license.jd
@@ -0,0 +1,143 @@
+page.title=License Agreement
+
+@jd:body
+
+<p>
+To get started with the Android SDK Preview, you must agree to the following terms and conditions. 
+As described below, please note that this is a preview version of the Android SDK, subject to change, that you use at your own risk.  The Android SDK Preview is not a stable release, and may contain errors and defects that can result in serious damage to your computer systems, devices and data.
+</p>
+
+<p>
+This is the Android SDK Preview License Agreement (the “License Agreement”).
+</p>
+<div class="sdk-terms" style="height:auto;border:0;padding:0;width:700px">
+1. Introduction
+
+1.1 The Android SDK Preview (referred to in the License Agreement as the “Preview” and specifically including the Android system files, packaged APIs, and Preview library files, if and when they are made available) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the Preview.
+
+1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
+
+1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
+
+2. Accepting the License Agreement
+
+2.1 In order to use the Preview, you must first agree to the License Agreement. You may not use the Preview if you do not accept the License Agreement.
+
+2.2 By clicking to accept and/or using the Preview, you hereby agree to the terms of the License Agreement.
+
+2.3 You may not use the Preview and may not accept the License Agreement if you are a person barred from receiving the Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Preview.
+
+2.4 If you will use the Preview internally within your company or organization you agree to be bound by the License Agreement on behalf of your employer or other entity, and you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Preview on behalf of your employer or other entity.
+
+3. Preview License from Google
+
+3.1 Subject to the terms of the License Agreement, Google grants you a royalty-free, non-assignable, non-exclusive, non-sublicensable, limited, revocable license to use the Preview, personally or internally within your company or organization, solely to develop applications to run on the Android platform.
+
+3.2 You agree that Google or third parties owns all legal right, title and interest in and to the Preview, including any Intellectual Property Rights that subsist in the Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
+
+3.3 You may not use the Preview for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Preview or any part of the Preview; or (b) load any part of the Preview onto a mobile handset or any other hardware device except a personal computer, combine any part of the Preview with other software, or distribute any software or device incorporating a part of the Preview.
+
+3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Preview.
+
+3.5 Use, reproduction and distribution of components of the Preview licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights.
+
+3.6 You agree that the form and nature of the Preview that Google provides may change without prior notice to you and that future versions of the Preview may be incompatible with applications developed on previous versions of the Preview. You agree that Google may stop (permanently or temporarily) providing the Preview (or any features within the Preview) to you or to users generally at Google's sole discretion, without prior notice to you.
+
+3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
+
+3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Preview.
+
+4. Use of the Preview by You
+
+4.1 Google agrees that nothing in the License Agreement gives Google any right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the Preview, including any intellectual property rights that subsist in those applications.
+
+4.2 You agree to use the Preview and write applications only for purposes that are permitted by (a) the License Agreement, and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). 
+
+4.3 You agree that if you use the Preview to develop applications, you will protect the privacy and legal rights of users. If users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If users provide you with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, each user has given you permission to do so.
+
+4.4 You agree that you will not engage in any activity with the Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of Google or any third party.
+
+4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
+
+4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
+
+4.7 The Preview is in development, and your testing and feedback are an important part of the development process. By using the Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Preview having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Preview as this Preview will no longer be supported after the official Android SDK is released.
+
+5. Your Developer Credentials
+
+5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
+
+6. Privacy and Information
+
+6.1 In order to continually innovate and improve the Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Preview are being used and how they are being used. Before any of this information is collected, the Preview will notify you and seek your consent. If you withhold consent, the information will not be collected.
+
+6.2 The data collected is examined in the aggregate to improve the Preview and is maintained in accordance with Google's Privacy Policy located at http://www.google.com/policies/privacy/.
+
+7. Third Party Applications
+
+7.1 If you use the Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
+
+7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
+
+7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party.
+
+8. Using Google APIs
+
+8.1 Google APIs
+
+8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
+
+8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
+
+9. Terminating the License Agreement
+
+9.1 the License Agreement will continue to apply until terminated by either you or Google as set out below.
+
+9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the Preview and any relevant developer credentials.
+
+9.3 Google may at any time, terminate the License Agreement, with or without cause, upon notice to you.
+
+9.4 The License Agreement will automatically terminate without notice or other action upon the earlier of:
+(A) when Google ceases to provide the Preview or certain parts of the Preview to users in the country in which you are resident or from which you use the service; and
+(B) Google issues a final release version of the Android SDK.
+
+9.5 When the License Agreement is terminated, the license granted to you in the License Agreement will terminate, you will immediately cease all use of the Preview, and the provisions of paragraphs 10, 11, 12 and 14 shall survive indefinitely.
+
+10. DISCLAIMERS
+
+10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE PREVIEW IS AT YOUR SOLE RISK AND THAT THE PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
+
+10.2 YOUR USE OF THE PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. WITHOUT LIMITING THE FOREGOING, YOU UNDERSTAND THAT THE PREVIEW IS NOT A STABLE RELEASE AND MAY CONTAIN ERRORS, DEFECTS AND SECURITY VULNERABILITIES THAT CAN RESULT IN SIGNIFICANT DAMAGE, INCLUDING THE COMPLETE, IRRECOVERABLE LOSS OF USE OF YOUR COMPUTER SYSTEM OR OTHER DEVICE.
+
+10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+
+11. LIMITATION OF LIABILITY
+
+11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
+
+12. Indemnification
+
+12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Preview, (b) any application you develop on the Preview that infringes any Intellectual Property Rights of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you of the License Agreement.
+
+13. Changes to the License Agreement
+
+13.1 Google may make changes to the License Agreement as it distributes new versions of the Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Preview is made available.
+
+14. General Legal Terms
+
+14.1 the License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Preview.
+
+14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
+
+14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable.
+
+14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement.
+
+14.5 EXPORT RESTRICTIONS. THE PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
+
+14.6 The License Agreement may not be assigned or transferred by you without the prior written approval of Google, and any attempted assignment without such approval will be void. You shall not delegate your responsibilities or obligations under the License Agreement without the prior written approval of Google.
+
+14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
+
+
+</div>
\ No newline at end of file
diff --git a/docs/html/preview/material/animations.jd b/docs/html/preview/material/animations.jd
new file mode 100644
index 0000000..b8d063b
--- /dev/null
+++ b/docs/html/preview/material/animations.jd
@@ -0,0 +1,441 @@
+page.title=Animations
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#touch">Touch Feedback</a></li>
+  <li><a href="#reveal">Reveal Effect</a></li>
+  <li><a href="#transitions">Activity Transitions</a></li>
+  <li><a href="#curvedmotion">Curved Motion</a></li>
+  <li><a href="#viewstate">Animating View State Changes</a></li>
+  <li><a href="#drawabletint">Drawable Tinting</a></li>
+  <li><a href="#colorextract">Extracting Colors from an Image</a></li>
+</ol>
+</div>
+</div>
+
+<p>Animations in material design give users feedback on their actions and provide visual
+continuity as users interact with your app. The material theme provides some default animations
+for buttons and activity transitions, and the Android L Developer Preview provides additional
+APIs that let you customize these animations and create new ones:</p>
+
+<ul>
+<li>Touch feedback</li>
+<li>Reveal effect</li>
+<li>Activity transitions</li>
+<li>Curved motion</li>
+<li>View state changes</li>
+</ul>
+
+
+<h2 id="touch">Touch Feedback</h2>
+
+<p>The default touch feedback animations for buttons use the new
+<code>RippleDrawable</code> class, which transitions between different states with a ripple
+effect.</p>
+
+<p>In most cases, this functionality should be applied in your view XML by specifying the
+background as <code>?android:attr/selectableItemBackground</code> for a bounded ripple or
+<code>?android:attr/selectableItemBackgroundBorderless</code> for a ripple that extends beyond
+the view bounds. You can also create a <code>RippleDrawable</code> and set
+it as the background of your view. Alternatively, you can define a <code>RippleDrawable</code>
+as an XML resource using the <code>ripple</code> element. The
+Android L Developer Preview animates the selection color with a ripple effect.</p>
+
+<p>You can assign a color to <code>RippleDrawable</code> objects. To change the default touch
+feedback color, use the theme's <code>android:colorControlHighlight</code> attribute.</p>
+
+
+<h2 id="reveal">Reveal Effect</h2>
+
+<p>The <code>ViewAnimationUtils.createCircularReveal</code> method enables you to animate a
+clipping circle to reveal or hide a view.</p>
+
+<p>To reveal a previously invisible view using this effect:</p>
+
+<pre>
+// previously invisible view
+View myView = findViewById(R.id.my_view);
+
+// get the center for the clipping circle
+int cx = (myView.getLeft() + myView.getRight()) / 2;
+int cy = (myView.getTop() + myView.getBottom()) / 2;
+
+// get the final radius for the clipping circle
+int finalRadius = myView.getWidth();
+
+// create and start the animator for this view
+// (the start radius is zero)
+ValueAnimator anim =
+    ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
+anim.start();
+</pre>
+
+<p>To hide a previously visible view using this effect:</p>
+
+<pre>
+// previously visible view
+final View myView = findViewById(R.id.my_view);
+
+// get the center for the clipping circle
+int cx = (myView.getLeft() + myView.getRight()) / 2;
+int cy = (myView.getTop() + myView.getBottom()) / 2;
+
+// get the initial radius for the clipping circle
+int initialRadius = myView.getWidth();
+
+// create the animation (the final radius is zero)
+ValueAnimator anim =
+    ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
+
+// make the view invisible when the animation is done
+anim.addListener(new AnimatorListenerAdapter() {
+    &#64;Override
+    public void onAnimationEnd(Animator animation) {
+        super.onAnimationEnd(animation);
+        myView.setVisibility(View.INVISIBLE);
+    }
+});
+
+// start the animation
+anim.start();
+</pre>
+
+
+<h2 id="transitions">Activity Transitions</h2>
+
+<p>You can specify custom animations for enter and exit transitions and for
+transitions of shared elements between activities.</p>
+
+<ul>
+<li>An <strong>enter</strong> transition determines how views in an activity enter the scene.
+For example, in the <em>explode</em> enter transition, the views enter the scene from the outside
+and fly in towards the center of the screen.</li>
+
+<li>An <strong>exit</strong> transition determines how views in an activity exit the scene. For
+  example, in the <em>explode</em> exit transition, the views exit the scene away from the
+center.</li>
+
+<li>A <strong>shared elements</strong> transition determines how views that are shared between
+two activities transition between these activities. For example, if two activities have the same
+image in different positions and sizes, the <em>moveImage</em> shared element transition
+translates and scales the image smoothly between these activities.</li>
+</ul>
+
+<p>The Android L Developer Preview supports these enter and exit transitions:</p>
+
+<ul>
+<li><em>explode</em> - Moves views in or out from the center of the scene.</li>
+<li><em>slide</em> - Moves views in or out from one of the edges of the scene.</li>
+<li><em>fade</em> - Mades views in or out of the scene.</li>
+</ul>
+
+<p>Any transition that extends the <code>android.transition.Visibility</code> class is supported
+as an enter or exit transition. For more information, see the API reference for the
+<code>android.transition.Transition</code> class.</p>
+
+<p>The Android L Developer Preview also supports these shared elements transitions:</p>
+
+<ul>
+<li><em>changeBounds</em> - Animates the changes in layout bounds of target views.</li>
+<li><em>changeClipBounds</em> - Animates the changes in clip bounds of target views.</li>
+<li><em>changeTransform</em> - Animates the changes in scale and rotation of target views.</li>
+<li><em>moveImage</em> - Animates changes in size and scale type for an image view.</li>
+</ul>
+
+<p>When you enable activity transitions in your app, the default cross-fading transition is
+activated between the entering and exiting activities.</p>
+
+<img src="/preview/material/images/SceneTransition.png" alt=""
+     id="figure1" style="width:600px;margin-top:20px"/>
+<p class="img-caption">
+  <strong>Figure 1</strong> - A scene transition with one shared element.
+</p>
+
+<h3>Specify custom transitions</h3>
+
+<p>First, enable window content transitions with the <code>android:windowContentTransitions</code>
+attribute when you define a style that inherits from the material theme. You can also specify
+enter, exit, and shared element transitions in your style definition:</p>
+
+<pre>
+&lt;style name="BaseAppTheme" parent="android:Theme.Material">
+  &lt;!-- enable window content transitions -->
+  &lt;item name="android:windowContentTransitions">true&lt;/item>
+
+  &lt;!-- specify enter and exit transitions -->
+  &lt;item name="android:windowEnterTransition">@transition/explode&lt;/item>
+  &lt;item name="android:windowExitTransition">@transition/explode&lt;/item>
+
+  &lt;!-- specify shared element transitions -->
+  &lt;item name="android:windowSharedElementEnterTransition">
+    &#64;transition/move_image&lt;/item>
+  &lt;item name="android:windowSharedElementExitTransition">
+    &#64;transition/move_image&lt;/item>
+&lt;/style>
+</pre>
+
+<p>The <code>move_image</code> transition in this example is defined as follows:</p>
+
+<pre>
+&lt;!-- res/transition/move_image.xml -->
+&lt;!-- (see also Shared Transitions below) -->
+&lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
+  &lt;moveImage/>
+&lt;/transitionSet>
+</pre>
+
+<p>The <code>moveImage</code> element corresponds to the <code>android.transition.MoveImage</code>
+class. For more information, see the API reference for <code>android.transition.Transition</code>.
+</p>
+
+<p>To enable window content transitions in your code instead, call the
+<code>Window.requestFeature</code> method:</p>
+
+<pre>
+// inside your activity (if you did not enable transitions in your theme)
+getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
+
+// set an exit transition
+getWindow().setExitTransition(new Explode());
+</pre>
+
+<p>To specify transitions in your code, call these methods with a <code>Transition</code>
+object:</p>
+
+<ul>
+  <li><code>Window.setEnterTransition</code></li>
+  <li><code>Window.setExitTransition</code></li>
+  <li><code>Window.setSharedElementEnterTransition</code></li>
+  <li><code>Window.setSharedElementExitTransition</code></li>
+</ul>
+
+<p>The <code>setExitTransition</code> and <code>setSharedElementExitTransition</code> methods
+define the exit transition for the calling activity. The <code>setEnterTransition</code> and
+<code>setSharedElementEnterTransition</code> methods define the enter transition for the called
+activity.</p>
+
+<p>To get the full effect of a transition, you must enable window content transitions on both the
+calling and called activities. Otherwise, the calling activity will start the exit transition,
+but then you'll see a window transition (like scale or fade).</p>
+
+<p>To start an enter transition as soon as possible, use the
+<code>Window.setAllowEnterTransitionOverlap</code> method on the called activity. This lets you
+have more dramatic enter transitions. The same applies for the calling activity and exit
+transitions with the <code>Window.setAllowExitTransitionOverlap</code> method.</p>
+
+<h3>Start an activity using transitions</h3>
+
+<p>If you enable transitions and set an exit transition for an activity, the transition is activated
+when you launch another activity with the <code>startActivity</code> method. If you have set an
+enter transition for the second activity, the transition is also activated when the activity
+starts.</p>
+
+<h3>Shared elements transitions</h3>
+
+<p>To make a screne transition animation between two activities that have a shared element:</p>
+
+<ol>
+<li>Enable window content transitions in your style.</li>
+<li>Specify a shared elements transition in your style.</li>
+<li>Define your transition as an XML resource.</li>
+<li>Assign a common name to the shared elements in both layouts with the
+    <code>android:viewName</code> attribute.</li>
+<li>Use the <code>ActivityOptions.makeSceneTransitionAnimation</code> method.</li>
+</ol>
+
+<pre>
+// get the element that receives the click event
+final View imgContainerView = findViewById(R.id.img_container);
+
+// get the common element for the transition in this activity
+final View androidRobotView = findViewById(R.id.image_small);
+
+// define a click listener
+imgContainerView.setOnClickListener(new View.OnClickListener() {
+    &#64;Override
+    public void onClick(View view) {
+        Intent intent = new Intent(this, Activity2.class);
+        // create the transition animation - the images in the layouts
+        // of both activities are defined with android:viewName="robot"
+        ActivityOptions options = ActivityOptions
+            .makeSceneTransitionAnimation(this, androidRobotView, "robot");
+        // start the new activity
+        startActivity(intent, options.toBundle());
+    }
+});
+</pre>
+
+<p>For shared dynamic views that you generate in your code, use the <code>View.setViewName</code>
+method to specify a common element name in both activities.</p>
+
+<p>To reverse the scene transition animation when you finish the second activity, call the
+<code>Activity.finishAfterTransition</code> method instead of <code>Activity.finish</code>.</p>
+
+<h3>Multiple shared elements</h3>
+
+<p>To make a scene transition animation between two activities that have more than one shared
+element, define the shared elements in both layouts with the <code>android:viewName</code>
+attribute (or use the <code>View.setViewName</code> in both activities), and create an
+<code>ActivityOptions</code> object as follows:</p>
+
+<pre>
+ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
+        Pair.create(view1, "agreedName1"),
+        Pair.create(view2, "agreedName2"));
+</pre>
+
+
+<h2 id="curvedmotion">Curved Motion</h2>
+
+<p>Animations in material design rely on curves for time interpolation and spatial movement
+patterns. The Android L Developer Preview provides new APIs that enable you to define custom
+timing curves and curved motion patterns for animations.</p>
+
+<p>The <code>PathInterpolator</code> class is a new interpolator based on a Bézier curve or a
+<code>Path</code> object. This interpolator specifies a motion curve in a 1x1 square, with anchor
+points at (0,0) and (1,1) and control points as specified using the constructor arguments. You can
+also define a <code>PathInterpolator</code> as an XML resource:</p>
+
+<pre>
+&lt;pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.4"
+    android:controlY1="0"
+    android:controlX2="1"
+    android:controlY2="1"/>
+</pre>
+
+<p>The Android L Developer Preview provides XML resources for the three basic curves in the
+material design specification:</p>
+
+<ul>
+  <li><code>&#64;interpolator/fast_out_linear_in.xml</code></li>
+  <li><code>&#64;interpolator/fast_out_slow_in.xml</code></li>
+  <li><code>&#64;interpolator/linear_out_slow_in.xml</code></li>
+</ul>
+
+<p>You can pass a <code>PathInterpolator</code> object to the
+<code>Animator.setInterpolation</code> method.</p>
+
+<p>The <code>ObjectAnimator</code> class has new constructors that enable you to animate
+coordinates along a path using two or more properties at once. For example, the following animator
+uses a <code>Path</code> object to animate the X and Y properties of a view:</p>
+
+<pre>
+ObjectAnimator mAnimator;
+mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
+...
+mAnimator.start();
+</pre>
+
+
+<h2 id="viewstate">Animating View State Changes</h2>
+
+<p>The new <code>StateListAnimator</code> class lets you define animators that run when the state
+of a view changes. The following example shows how to define an <code>StateListAnimator</code> as
+an XML resource:</p>
+
+<pre>
+&lt;!-- animate the translationZ property of a view when pressed -->
+&lt;selector xmlns:android="http://schemas.android.com/apk/res/android">
+  &lt;item android:state_pressed="true">
+    &lt;set>
+      &lt;objectAnimator android:propertyName="translationZ"
+        android:duration="100"
+        android:valueTo="2"
+        android:valueType="floatType"/>
+        &lt;!-- you could have other objectAnimator elements
+             here for "x" and "y", or other properties -->
+    &lt;/set>
+  &lt;/item>
+  &lt;item android:state_enabled="true"
+    android:state_pressed="false"
+    android:state_focused="true">
+    &lt;set>
+      &lt;objectAnimator android:propertyName="translationZ"
+        android:duration="100"
+        android:valueTo="2"
+        android:valueType="floatType"/>
+    &lt;/set>
+  &lt;/item>
+&lt;/selector>
+</pre>
+
+<p class="note"><strong>Note:</strong> There is a known issue in the L Developer Preview release
+that requires valueFrom values to be provided in StateListAnimator animations to get the correct
+behavior.</p>
+
+<p>The new <code>AnimatedStateListDrawable</code> class lets you create drawables that show
+animations between state changes of the associated view. Some of the system widgets in the
+Android L Developer Preview use these animations by default. The following example shows how
+to define an <code>AnimatedStateListDrawable</code> as an XML resource:</p>
+
+<pre>
+&lt;!-- res/drawable/myanimstatedrawable.xml -->
+&lt;animated-selector
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    &lt;!-- provide a different drawable for each state-->
+    &lt;item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
+        android:state_pressed="true"/>
+    &lt;item android:id="@+id/focused" android:drawable="@drawable/drawableF"
+        android:state_focused="true"/>
+    &lt;item android:id="@id/default"
+        android:drawable="@drawable/drawableD"/>
+
+    &lt;!-- specify a transition -->
+    &lt;transition android:fromId="@+id/default" android:toId="@+id/pressed">
+        &lt;animation-list>
+            &lt;item android:duration="15" android:drawable="@drawable/dt1"/>
+            &lt;item android:duration="15" android:drawable="@drawable/dt2"/>
+            ...
+        &lt;/animation-list>
+    &lt;/transition>
+    ...
+&lt;/animated-selector>
+</pre>
+
+
+<h2 id="drawabletint">Drawable Tinting</h2>
+
+<p>The Android L Developer Preview enables you to define bitmaps or nine-patches as alpha masks and
+to tint them using a color resource or a theme attribute that resolves to a color resource (for
+example, <code>?android:attr/colorPrimary</code>). You can create these assets only once and color them
+automatically to match your theme.</p>
+
+<p>To apply a tint to a bitmap, use the <code>setTint</code> method or the <code>android:tint</code>
+attribute for <code>BitmapDrawable</code> and <code>NinePatchDrawable</code>.</p>
+
+<p>The <code>setTint</code> method also lets you set the Porter-Duff mode used to blend the
+tint color for <code>NinePatchDrawable</code> and <code>BitmapDrawable</code> objects in your code.
+To set the tint mode in your layouts, use the <code>android:tintMode</code> attribute.</p>
+
+
+<h2 id="colorextract">Extracting Prominent Colors from an Image</h2>
+
+<p>The Android L Developer Preview Support Library includes the <code>Palette</code> class,
+which lets you extract prominent colors from an image. This class extracts the following
+prominent colors:</p>
+
+<ul>
+<li>Vibrant</li>
+<li>Vibrant dark</li>
+<li>Vibrant light</li>
+<li>Muted</li>
+<li>Muted dark</li>
+<li>Muted light</li>
+</ul>
+
+<p>To extract these colors, pass a <code>Bitmap</code> object to the
+<code>Palette.generate</code> static method in the background thread where you load your images.
+If you can't use that thread, call the <code>Palette.generateAsync</code> method instead and
+provide a listener.</p>
+
+<p>To retrieve the prominent colors from the image, use the getter methods in the
+<code>Palette</code> class, such as <code>Palette.getVibrantColor</code>.</p>
+
+<p>For more information, see the API reference for the
+<code>android.support.v7.graphics.Palette</code> class.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/compatibility.jd b/docs/html/preview/material/compatibility.jd
new file mode 100644
index 0000000..fb97112
--- /dev/null
+++ b/docs/html/preview/material/compatibility.jd
@@ -0,0 +1,82 @@
+page.title=Compatibility
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#materialtheme">Material Theme</a></li>
+  <li><a href="#layouts">Layouts</a></li>
+  <li><a href="#widgets">UI Widgets</a></li>
+  <li><a href="#animation">Animation APIs</a></li>
+</ol>
+</div>
+</div>
+
+<p>The new material design features (like the material theme and activity transitions) are only
+available in the Android L Developer Preview. However, you can design your apps to make use of
+these features when running on devices with the Android L Developer Preview and still be
+compatible with previous releases of Android.</p>
+
+
+<h2 id="materialtheme">Material Theme</h2>
+
+<p>The material theme is only available in the Android L Developer Preview. To configure your
+app to use the material theme on devices running the Android L Developer Preview and an older
+theme on devices running earlier versions of Android:</p>
+
+<ol>
+<li>Define a theme that inherits from an older theme (like Holo) in
+<code>res/values/styles.xml</code>.</li>
+<li>Define a theme with the same name that inherits from the material theme in
+<code>res/values-v21/styles.xml</code>.</li>
+<li>Set this theme as your app's theme in the manifest file.</li>
+</ol>
+
+<p class="note"><strong>Note:</strong> If you do not provide an alternative theme in this manner,
+your app will not run on earlier versions of Android.</p>
+
+
+<h2 id="layouts">Layouts</h2>
+
+<p>If the layouts that you design according to the material design guidelines do not use any
+of the new XML attributes from the Android L Developer Preview, they will work on previous
+versions of Android. Otherwise, you can provide alternative layouts. You can also provide
+alternative layouts to customize how your app looks on earlier versions of Android.</p>
+
+<p>Create your layout files for the Android L Developer Preview inside <code>res/layout-v21/</code>
+and your alternative layout files for earlier versions of Android inside <code>res/layout/</code>.
+Alternative layouts have the same file name.</p>
+
+<p>To avoid duplication of code, define your styles inside <code>res/values/</code> and modify the
+styles in <code>res/values-v21/</code> for the new APIs.</p>
+
+
+<h2 id="widgets">UI Widgets</h2>
+
+<p>The <code>RecyclerView</code> and <code>CardView</code> widgets are included in the Android L
+Developer Preview Support Library, so they are available in earlier versions of Android with
+these limitations:</p>
+
+<ul>
+<li><code>CardView</code> falls back to a programmatic shadow implementation using additional padding.</li>
+<li><code>CardView</code> does not clip its children views that intersect with rounded corners.</li>
+</ul>
+
+<p>These limitations do not apply to the Android L Developer Preview.</p>
+
+
+<h2 id="animation">Animation APIs</h2>
+
+<p>The following new APIs are only available in the Android L Developer Preview:</p>
+
+<ul>
+<li>Activity transitions</li>
+<li>Touch feedback</li>
+<li>Reveal animations</li>
+<li>Path-based animations</li>
+</ul>
+
+<p>To preserve compatibility with earlier verisons of Android, check the system version at
+runtime before you invoke these APIs.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/get-started.jd b/docs/html/preview/material/get-started.jd
new file mode 100644
index 0000000..7d0625e
--- /dev/null
+++ b/docs/html/preview/material/get-started.jd
@@ -0,0 +1,147 @@
+page.title=Get Started
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#applytheme">Apply the Material Theme</a></li>
+  <li><a href="#layouts">Design Your Layouts</a></li>
+  <li><a href="#depth">Specify Elevation in Your Views</a></li>
+  <li><a href="#widgets">Use the New UI Widgets</a></li>
+  <li><a href="#animations">Customize Your Animations</a></li>
+</ol>
+</div>
+</div>
+
+<p>To create apps with material design:</p>
+
+<ol>
+  <li style="margin-bottom:10px">
+    Take a look at the <a href="http://www.google.com/design/spec">material design
+    specification</a>.</li>
+  <li style="margin-bottom:10px">
+    Apply the material <strong>theme</strong> to your app.</li>
+  <li style="margin-bottom:10px">
+    Define additional <strong>styles</strong> to customize the material theme.</li>
+  <li style="margin-bottom:10px">
+    Create your <strong>layouts</strong> following material design guidelines.</li>
+  <li style="margin-bottom:10px">
+    Specify the <strong>elevation</strong> of your views to cast appropriate shadows.</li>
+  <li style="margin-bottom:10px">
+    Use the new <strong>widgets</strong> for complex views, such as lists and cards.</li>
+  <li style="margin-bottom:10px">
+    Use the new APIs to customize the <strong>animations</strong> in your app.</li>
+</ol>
+
+<h3>Update Your App for the Android L Developer Preview</h3>
+
+<p>To update an existing app for the Android L Developer Preview, design new layouts following
+material design guidelines and consider how you can improve the user experience for your app by
+incorporating depth, touch feedback and animations in your UI.</p>
+
+<h3>Create New Apps for the Android L Developer Preview</h3>
+
+<p>If you are creating a new app for the Android L Developer Preview, the <a
+href="http://www.google.com/design/spec">material design guidelines</a> provide you with a
+cohesive design framework for your app. Follow these guidelines and
+use the new functionality in the Android framework to design and develop your app.</p>
+
+
+<h2 id="applytheme">Apply the Material Theme</h2>
+
+<p>To apply the material theme in your app, specify a style that inherits from
+<code>android:Theme.Material</code>:</p>
+
+<pre>
+&lt;!-- res/values/styles.xml -->
+&lt;resources>
+  &lt!-- your app's theme inherits from the Material theme -->
+  &lt;style name="AppTheme" parent="android:Theme.Material">
+    &lt!-- theme customizations -->
+  &lt;/style>
+&lt;/resources>
+</pre>
+
+<p>The material theme provides new system widgets that let you set their color palette and default
+animations for touch feedback and activity transitions. For more details, see
+<a href="{@docRoot}preview/material/theme.html">Material Theme</a>.</p>
+
+
+<h2 id="layouts">Design Your Layouts</h2>
+
+<p>In addition to applying and customizing the material theme, your layouts should conform to
+the <a href="http://www.google.com/design/spec">material design guidelines</a>. When you design
+your layouts, pay special attention to the following:</p>
+
+<ul>
+<li>Baseline grids</li>
+<li>Keylines</li>
+<li>Spacing</li>
+<li>Touch target size</li>
+<li>Layout structure</li>
+</ul>
+
+
+<h2 id="depth">Specify Elevation in Your Views</h2>
+
+<p>Views can cast shadows, and the elevation value of a view
+determines the size of its shadow and its drawing order. To set the elevation of a view, use the
+<code>android:elevation</code> attribute in your layouts:</p>
+
+<pre>
+&lt;TextView
+    android:id="@+id/my_textview"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/next"
+    android:background="@color/white"
+    <strong>android:elevation</strong>="5dp" />
+</pre>
+
+<p>The new <code>translationZ</code> property lets you create animations that reflect temporary
+changes in the elevation of a view. For example, this is useful to respond to touch gestures.</p>
+
+<p>For more details, see <a href="{@docRoot}preview/material/views-shadows.html">Views and
+Shadows</a>.</p>
+
+
+<h2 id="widgets">Use the New UI Widgets</h2>
+
+<p><code>RecyclerView</code> is a more advanced version of <code>ListView</code> that provides
+performance improvements and is easier to use. <code>CardView</code> lets you show pieces of
+information inside cards with a consistent look across apps. To include a <code>CardView</code>
+in your layout:</p>
+
+<pre>
+&lt;android.support.v7.widget.CardView
+    android:id="@+id/card_view"
+    android:layout_width="200dp"
+    android:layout_height="200dp"
+    card_view:cardCornerRadius="3dp">
+    ...
+&lt;/android.support.v7.widget.CardView>
+</pre>
+
+<p>For more information, see <a href="{@docRoot}preview/material/ui-widgets.html">UI Widgets</a>.</p>
+
+
+<h2 id="animations">Customize Your Animations</h2>
+
+<p>The Android L Developer Preview includes new APIs to create custom animations in your app.
+For example, you can enable activity transitions and define an exit transition inside an
+activity:</p>
+
+<pre>
+// inside your activity
+getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
+
+// set an exit transition
+getWindow().setExitTransition(new Explode());
+</pre>
+
+<p>When you start another activity from this activity, the exit transition is activated.</p>
+
+<p>To learn about all the features in the new APIs, see <a
+href="{@docRoot}preview/material/animations.html">Animations</a>.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/images/MaterialDark.png b/docs/html/preview/material/images/MaterialDark.png
new file mode 100644
index 0000000..f1018af
--- /dev/null
+++ b/docs/html/preview/material/images/MaterialDark.png
Binary files differ
diff --git a/docs/html/preview/material/images/MaterialLight.png b/docs/html/preview/material/images/MaterialLight.png
new file mode 100644
index 0000000..4ed7d5c
--- /dev/null
+++ b/docs/html/preview/material/images/MaterialLight.png
Binary files differ
diff --git a/docs/html/preview/material/images/RecyclerView.png b/docs/html/preview/material/images/RecyclerView.png
new file mode 100644
index 0000000..364951d
--- /dev/null
+++ b/docs/html/preview/material/images/RecyclerView.png
Binary files differ
diff --git a/docs/html/preview/material/images/SceneTransition.png b/docs/html/preview/material/images/SceneTransition.png
new file mode 100644
index 0000000..ecaf472
--- /dev/null
+++ b/docs/html/preview/material/images/SceneTransition.png
Binary files differ
diff --git a/docs/html/preview/material/images/ThemeColors.png b/docs/html/preview/material/images/ThemeColors.png
new file mode 100644
index 0000000..bbcecf2
--- /dev/null
+++ b/docs/html/preview/material/images/ThemeColors.png
Binary files differ
diff --git a/docs/html/preview/material/images/card_travel.png b/docs/html/preview/material/images/card_travel.png
new file mode 100644
index 0000000..19752a8
--- /dev/null
+++ b/docs/html/preview/material/images/card_travel.png
Binary files differ
diff --git a/docs/html/preview/material/images/list_mail.png b/docs/html/preview/material/images/list_mail.png
new file mode 100644
index 0000000..e70291c
--- /dev/null
+++ b/docs/html/preview/material/images/list_mail.png
Binary files differ
diff --git a/docs/html/preview/material/index.jd b/docs/html/preview/material/index.jd
new file mode 100644
index 0000000..9628e3a
--- /dev/null
+++ b/docs/html/preview/material/index.jd
@@ -0,0 +1,130 @@
+page.title=Material Design
+page.type=design
+page.image=images/material.png
+page.metaDescription=Learn how to apply material design to your apps.
+
+@jd:body
+
+<p itemprop="description">The Android L Developer Preview includes support for material design
+apps. Material design is a comprehensive guide for visual, motion, and interaction design across
+platforms and devices. To use material design in your Android apps, follow the guidelines defined
+in the <a href="http://www.google.com/design/spec">material design specification</a> and use the
+new components and functionality available in the Android L Developer Preview.</p>
+
+<p>The Android L Developer Preview provides the following elements for you to build material
+design apps:</p>
+
+<ul>
+  <li>A new theme</li>
+  <li>New widgets for complex views</li>
+  <li>New APIs for custom shadows and animations</li>
+</ul>
+
+
+<h3>Material Theme</h3>
+
+<p>The material theme provides a new style for your app, system widgets that let you set
+their color palette, and default animations for touch feedback and activity transitions.</p>
+
+<!-- two columns -->
+<div style="width:700px;margin-top:25px;margin-bottom:20px">
+<div style="float:left;width:250px;margin-left:40px;margin-right:60px;">
+  <img src="{@docRoot}preview/material/images/MaterialDark.png" width="500" height="238"/>
+  <div style="width:140px;margin:0 auto">
+  <p style="margin-top:8px">Dark Material theme</p>
+  </div>
+</div>
+<div style="float:left;width:250px;margin-right:0px;">
+  <img src="{@docRoot}preview/material/images/MaterialLight.png" width="500" height="238"/>
+  <div style="width:140px;margin:0 auto">
+  <p style="margin-top:8px">Light Material theme</p>
+  </div>
+</div>
+<br style="clear:left"/>
+</div>
+
+
+<h3>New Widgets</h3>
+
+<p>The Android L Developer Preview includes two new widgets for displaying complex views:</p>
+
+<!-- two columns -->
+<div style="width:700px;margin-top:25px;margin-bottom:20px">
+<div style="float:left;width:250px;margin-left:40px;margin-right:60px;">
+  <img src="{@docRoot}preview/material/images/list_mail.png" width="500" height="426"/>
+  <p>The new <code>RecyclerView</code> widget is a more advanced version of <code>ListView</code>
+  that provides performance improvements for dynamic views and is easier to use.</p>
+</div>
+<div style="float:left;width:250px;margin-right:0px;">
+  <img src="{@docRoot}preview/material/images/card_travel.png" width="500" height="426"/>
+  <p>The new <code>CardView</code> widget lets you display important pieces of information inside
+  cards that have a consistent look and feel.</p>
+</div>
+<br style="clear:left"/>
+</div>
+
+
+<h3>View Shadows</h3>
+
+<p>In addition to the X and Y properties, views in the Android L Developer Preview have a Z
+property. This new property represents the elevation of a view, which determines:</p>
+
+<ul>
+<li>The size of the shadow - Views with higher Z values cast bigger shadows.</li>
+<li>The drawing order - Views with higher Z values appear on top of other views.</li>
+</ul>
+
+<div style="width:290px;margin-left:35px;float:right">
+  <div class="framed-nexus5-port-span-5">
+  <video class="play-on-hover" autoplay>
+    <source src="/preview/material/videos/ContactsAnim.mp4"/>
+    <source src="/preview/material/videos/ContactsAnim.webm"/>
+    <source src="/preview/material/videos/ContactsAnim.ogv"/>
+  </video>
+  </div>
+  <div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
+    <em>Click on the device screen to replay the movie</em>
+  </div>
+</div>
+
+<h3>Animations</h3>
+
+<p>The Android L Developer Preview provides new APIs that let you create custom animations for
+touch feedback in UI controls, view state changes, and activity transitions.</p>
+
+<p>The new animation APIs let you:</p>
+
+<ul>
+<li style="margin-bottom:15px">
+Respond to touch events in your views with <strong>touch feedback</strong> animations.
+</li>
+<li style="margin-bottom:15px">
+Hide and show views with <strong>reveal effect</strong> animations.
+</li>
+<li style="margin-bottom:15px">
+Switch between activities with custom <strong>activity transition</strong> animations.
+</li>
+<li style="margin-bottom:15px">
+Create more natural animations with <strong>curved motion</strong>.
+</li>
+<li style="margin-bottom:15px">
+Animate changes in one or more view properties with <strong>view state change</strong> animations.
+</li>
+<li style="margin-bottom:15px">
+Show animations in <strong>state list drawables</strong> between view state changes.
+</li>
+</ul>
+
+<p>Touch feedback animations are built into several standard views, such as buttons. The new APIs
+let you customize these animations and add animations to your custom views.</p>
+
+
+<h3>New Capabilities for Drawables</h3>
+
+<p>The Android L Developer Preview supports <strong>drawable tinting</strong>: you can define
+bitmaps as an alpha mask and tint them using a color resource. You create these assets only
+once and color each instance to match your theme. Drawables also now support specifying most XML
+properties as <strong>theme attributes</strong>.</p>
+
+<p>The Android L Developer Preview Support Library includes a <strong>color extraction</strong>
+library that lets you automatically extract prominent colors from a bitmap image.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/theme.jd b/docs/html/preview/material/theme.jd
new file mode 100644
index 0000000..740bf56
--- /dev/null
+++ b/docs/html/preview/material/theme.jd
@@ -0,0 +1,102 @@
+page.title=Material Theme
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#colorpalette">Customize the Colot Palette</a></li>
+  <li><a href="#statusbar">Customize the Status Bar</a></li>
+  <li><a href="#inheritance">Theme Individual Views</a></li>
+</ol>
+</div>
+</div>
+
+<p>The new material theme provides:</p>
+
+<ul>
+  <li>System widgets that let you set their color palette</li>
+  <li>Touch feedback animations for the system widgets</li>
+  <li>Activity transition animations</li>
+</ul>
+
+<p>You can customize the look of the material theme
+according to your brand identity with a color palette you control. You can tint the action bar and
+the status bar using theme attributes, as shown in Figure 1.</p>
+
+<div style="float:right;margin-left:25px;margin-top:-50px">
+<img src="{@docRoot}preview/material/images/ThemeColors.png" style="width:250px"/>
+<p class="img-caption" style="margin-bottom:0px">
+<strong>Figure 1.</strong> Customizing the material theme.</p>
+</div>
+
+<p>The system widgets have a new design and touch feedback animations. You can customize the
+color palette, the touch feedback animations, and the activity transitions for your app.</p>
+
+<p>The material theme is defined as:</p>
+
+<ul>
+  <li><code>@android:style/Theme.Material</code> (dark version)</li>
+  <li><code>@android:style/Theme.Material.Light</code> (light version)</li>
+  <li><code>@android:style/Theme.Material.Light.DarkActionBar</code></li>
+</ul>
+
+<p>For a list of material styles that you can use, see the API reference for
+<code>android.R.style</code>.</p>
+
+<p class="note">
+<strong>Note:</strong> The material theme is only available in the Android L Developer Preview.
+For more information, see <a href="{@docRoot}preview/material/compatibility.html">Compatibility</a>.
+</p>
+
+
+<h2 id="colorpalette">Customize the Color Palette</h2>
+
+<p style="margin-bottom:30px">To customize the theme's base colors to fit your brand, define
+your custom colors using theme attributes when you inherit from the material theme:</p>
+
+<pre>
+&lt;resources>
+  &lt;!-- inherit from the material theme -->
+  &lt;style name="AppTheme" parent="android:Theme.Material">
+    &lt;!-- Main theme colors -->
+    &lt;!--   your app's branding color (for the app bar) -->
+    &lt;item name="android:colorPrimary">@color/primary&lt;/item>
+    &lt;!--   darker variant of colorPrimary (for status bar, contextual app bars) -->
+    &lt;item name="android:colorPrimaryDark">@color/primary_dark&lt;/item>
+    &lt;!--   theme UI controls like checkboxes and text fields -->
+    &lt;item name="android:colorAccent">@color/accent&lt;/item>
+  &lt;/style>
+&lt;/resources>
+</pre>
+
+
+<h2 id="statusbar">Customize the Status and Navigation Bar</h2>
+
+<p>The material theme lets you easily customize the status bar, so you can specify a
+color that fits your brand and provides enough contrast to show the white status icons. To
+set a custom color for the status bar, use the <code>android:statusBarColor</code> attribute when
+you extend the material theme. By default, <code>android:statusBarColor</code> inherits the
+value of <code>android:colorPrimaryDark</code>.</p>
+
+<p>To handle the color of the status bar yourself (for example, by adding a gradient in the
+background), set the <code>android:statusBarColor</code> attribute to
+<code>&#64;android:color/transparent</code> and adjust the window flags as required. You can
+also use the <code>Window.setStatusBarColor</code> method for animations or fading.</p>
+
+<p class="note"><strong>Note:</strong>
+The status bar should almost always have a clear delineation from the primary toolbar, except for
+full-bleed imagery cases and when you use a gradient as a protection.
+</p>
+
+<p>When customizing the navigation and status bars, make them both transparent or modify only
+the status bar. The navigation bar should remain black in all other cases.</p>
+
+
+<h2 id="inheritance">Theme Individual Views</h3>
+
+<p>Elements in XML layout definitions can specify the <code>android:theme</code> attribute,
+which references a theme resource. This attribute modifies the theme for the element and any
+elements inflated below it, which is useful to alter theme color palettes in a specific portion
+of an interface.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/ui-widgets.jd b/docs/html/preview/material/ui-widgets.jd
new file mode 100644
index 0000000..31604d6
--- /dev/null
+++ b/docs/html/preview/material/ui-widgets.jd
@@ -0,0 +1,198 @@
+page.title=UI Widgets
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#recyclerview">RecyclerView</a></li>
+  <li><a href="#cardview">CardView</a></li>
+</ol>
+</div>
+</div>
+
+<p>The support library in the Android L Developer Preview contains two new widgets,
+<code>RecyclerView</code> and <code>CardView</code>. Use these widgets to show complex lists
+and cards in your app. These widgets have material design style by default.</p>
+
+
+<h2 id="recyclerview">RecyclerView</h2>
+
+<p><code>RecyclerView</code> is a more advanced and flexible version of <code>ListView</code>.
+This widget is a container for large sets of views that can be recycled and scrolled very
+efficiently. Use the <code>RecyclerView</code> widget when you have lists with elements that
+change dynamically.</p>
+
+<p><code>RecyclerView</code> is easy to use, because it provides:</p>
+
+<ul>
+  <li>A layout manager for positioning items</li>
+  <li>Default animations for common item operations</li>
+</ul>
+
+<p>You also have the flexibility to define custom layout managers and animations for this
+widget.</p>
+
+<p>To use the <code>RecyclerView</code> widget, you have to specify an adapter and a layout
+manager. To create an adapter, you extend the <code>RecyclerView.Adapter</code> class. The details
+of the implementation depend on the specifics of your dataset and the type of views. For more
+information, see the <a href="#rvexamples">examples</a> below.</p>
+
+<img src="/preview/material/images/RecyclerView.png" alt="" id="figure1" style="width:550px"/>
+<p class="img-caption">
+  <strong>Figure 1</strong> - The <code>RecyclerView</code> widget.
+</p>
+
+<p>A <strong>layout manager</strong> positions item views inside a <code>RecyclerView</code> and
+determines when to reuse item views that are no longer visible to the user. To reuse (or
+<em>recycle</em>) a view, a layout manager may ask the adapter to replace the content of the
+view with a different element from the dataset. Recycling views in this manner improves
+performance by avoiding the creation of unnecessary views or performing expensive
+<code>findViewById</code> lookups.
+</p>
+
+<p><code>RecyclerView</code> provides <code>LinearLayoutManager</code>, which shows the items in a
+vertical or horizontal scrolling list. To create a custom layout, you extend the
+<code>RecyclerView.LayoutManager</code> class.</p>
+
+<h3>Animations</h3>
+
+<p>Animations for adding and removing items are enabled by default in <code>RecyclerView</code>.
+To customize these animations, extend the <code>RecyclerView.ItemAnimator</code> class and use
+the <code>RecyclerView.setItemAnimator</code> method.</p>
+
+<h3 id="rvexamples">Examples</h3>
+
+<p>To include a <code>RecyclerView</code> in your layout:</p>
+
+<pre>
+&lt;!-- A RecyclerView with some commonly used attributes -->
+&lt;android.support.v7.widget.RecyclerView
+    android:id="@+id/my_recycler_view"
+    android:scrollbars="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
+</pre>
+
+<p>To get the <code>RecyclerView</code> object in your activity:</p>
+
+<pre>
+public class MyActivity extends Activity {
+    private RecyclerView mRecyclerView;
+    private RecyclerView.Adapter mAdapter;
+    private RecyclerView.LayoutManager mLayoutManager;
+
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.my_activity);
+        mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
+
+        // improve performance if you know that changes in content
+        // do not change the size of the RecyclerView
+        mRecyclerView.setHasFixedSize(true);
+
+        // use a linear layout manager
+        mLayoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+
+        // specify an adapter (see also next example)
+        mAdapter = new MyAdapter(myDataset);
+        mRecyclerView.setAdapter(mAdapter);
+    }
+    ...
+}
+</pre>
+
+<p>To create a simple adapter:</p>
+
+<pre>
+public class MyAdapter extends RecyclerView.Adapter&lt;MyAdapter.ViewHolder> {
+    private String[] mDataset;
+
+    // Provide a reference to the type of views that you are using
+    // (custom viewholder)
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+        public TextView mTextView;
+        public ViewHolder(TextView v) {
+            super(v);
+            mTextView = v;
+        }
+    }
+
+    // Provide a suitable constructor (depends on the kind of dataset)
+    public MyAdapter(String[] myDataset) {
+        mDataset = myDataset;
+    }
+
+    // Create new views (invoked by the layout manager)
+    &#64;Override
+    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
+                                                   int viewType) {
+        // create a new view
+        View v = LayoutInflater.from(parent.getContext())
+                               .inflate(R.layout.my_text_view, null);
+        // set the view's size, margins, paddings and layout parameters
+        ...
+        ViewHolder vh = new ViewHolder(v);
+        return vh;
+    }
+
+    // Replace the contents of a view (invoked by the layout manager)
+    &#64;Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        // - get element from your dataset at this position
+        // - replace the contents of the view with that element
+        holder.mTextView.setText(mDataset[position]);
+
+    }
+
+    // Return the size of your dataset (invoked by the layout manager)
+    &#64;Override
+    public int getItemCount() {
+        return mDataset.length;
+    }
+}
+</pre>
+
+
+<h2 id="cardview">CardView</h2>
+
+<p><code>CardView</code> extends the <code>FrameLayout</code> class and lets you show information
+inside cards that have a consistent look on any app. <code>CardView</code> widgets can have
+shadows and rounded corners.</p>
+
+<p>To create a card with a shadow, use the <code>android:elevation</code> attribute.
+<code>CardView</code> uses real elevation and dynamic shadows
+and falls back to a programmatic shadow implementation on earlier versions. For more information,
+see <a href="{@docRoot}preview/material/compatibility.html">Compatibility</a>.</p>
+
+<p>Here's how to specify properties of <code>CardView</code>:</p>
+
+<ul>
+  <li>To set the corner radius in your layouts, use the <code>android:cardCornerRadius</code>
+  attribute.</li>
+  <li>To set the corner radius in your code, use the <code>CardView.setRadius</code> method.</li>
+  <li>To set the background color of a card, use the <code>android:cardBackgroundColor</code>
+attribute.</li>
+</ul>
+
+<p>To include a <code>CardView</code> in your layout:</p>
+
+<pre>
+&lt;!-- A CardView that contains a TextView -->
+&lt;android.support.v7.widget.CardView
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/card_view"
+    android:layout_gravity="center"
+    android:layout_width="200dp"
+    android:layout_height="200dp"
+    card_view:cardCornerRadius="4dp">
+
+    &lt;TextView
+        android:id="@+id/info_text"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+&lt;/android.support.v7.widget.CardView>
+</pre>
\ No newline at end of file
diff --git a/docs/html/preview/material/videos/ContactsAnim.mp4 b/docs/html/preview/material/videos/ContactsAnim.mp4
new file mode 100644
index 0000000..073f9dc
--- /dev/null
+++ b/docs/html/preview/material/videos/ContactsAnim.mp4
Binary files differ
diff --git a/docs/html/preview/material/videos/ContactsAnim.ogv b/docs/html/preview/material/videos/ContactsAnim.ogv
new file mode 100644
index 0000000..c5e751b
--- /dev/null
+++ b/docs/html/preview/material/videos/ContactsAnim.ogv
Binary files differ
diff --git a/docs/html/preview/material/videos/ContactsAnim.webm b/docs/html/preview/material/videos/ContactsAnim.webm
new file mode 100644
index 0000000..2a15ff5
--- /dev/null
+++ b/docs/html/preview/material/videos/ContactsAnim.webm
Binary files differ
diff --git a/docs/html/preview/material/views-shadows.jd b/docs/html/preview/material/views-shadows.jd
new file mode 100644
index 0000000..f7682f5
--- /dev/null
+++ b/docs/html/preview/material/views-shadows.jd
@@ -0,0 +1,95 @@
+page.title=Views and Shadows
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>In this document</h2>
+<ol>
+  <li><a href="#elevation">View Elevation</a></li>
+  <li><a href="#shadows">Shadows and Outlines</a></li>
+  <li><a href="#clip">Clipping Views</a></li>
+</ol>
+</div>
+</div>
+
+<p>The elevation of a view determines the size of its shadow:
+views with higher Z values cast bigger shadows. Views only cast shadows on the Z=0 plane under an
+orthographic projection (the views do not scale for different values of Z).</p>
+
+<p>Elevation is also useful to create animations where widgets temporarily rise above the
+view plane when performing some action.</p>
+
+
+<h2 id="elevation">View Elevation</h2>
+
+<p>The Z value for a view has two components, elevation and translation. The elevation is the
+static component, and the translation is used for animations:</p>
+
+<p><code>Z = elevation + translationZ</code></p>
+
+<p>To set the elevation of a view:</p>
+
+<ul>
+  <li>In a layout definition, use the <code>android:elevation</code> attribute.</li>
+  <li>In the code of an activity, use the <code>View.setElevation</code> method.</li>
+</ul>
+
+<p>To set the translation of a view, use the <code>View.setTranslationZ</code> method.</p>
+
+<p>The new <code>ViewPropertyAnimator.z</code> and <code>ViewPropertyAnimator.translationZ</code>
+methods enable you to easily animate the elevation of views. For more information, see
+the API reference for <code>ViewPropertyAnimator</code> and the <a
+href="{@docRoot}guide/topics/graphics/prop-animation.html#object-animator">Property Animation</a>
+developer guide.</p>
+
+<p>The Z values are measured in the same units as the X and Y values.</p>
+
+
+<h2 id="shadows">Shadows and Outlines</h2>
+
+<p>The bounds of a view's background drawable determine the default shape of its shadow.
+<strong>Outlines</strong> represent the outer shape of a graphics object and define the ripple
+area for touch feedback.</p>
+
+<p>For example, if you define a view with a background drawable:</p>
+
+<pre>
+&lt;TextView
+    android:id="@+id/myview"
+    ...
+    android:elevation="2dp"
+    android:background="@drawable/myrect" />
+</pre>
+
+<p>where the background drawable is defined as a rectangle with rounded corners:</p>
+
+<pre>
+&lt;!-- res/drawable/myrect.xml -->
+&lt;shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    &lt;solid android:color="#42000000" />
+    &lt;corners android:radius="5dp" />
+&lt;/shape>
+</pre>
+
+<p>Then this view and drawable cast the appropiate shadow.</p>
+
+<p>You can also create outlines in your code using the methods in the <code>Outline</code> class,
+and you can assign them to views with the <code>View.setOutline</code> method.</p>
+
+<p>To prevent a view from casting a shadow, set its outline to <code>null</code>.</p>
+
+
+<h2 id="clip">Clipping Views</h2>
+
+<p>Clip a view to its outline area using the
+<code>View.setClipToOutline</code> method. Only rectangle, circle, and round rectangle outlines
+support clipping, as determined by the <code>Outline.canClip</code> method.</p>
+
+<p>To clip a view to the shape of a drawable, set the drawable as the background of the view
+(as shown above) and call the <code>View.setClipToOutline</code> method.</p>
+
+<p>Because clipping views is an expensive operation, don't animate the shape you use to
+clip a view. To achieve this effect, use a <a
+href="{@docRoot}preview/material/animations.html#reveal">Reveal Effect</a> animation.</p>
\ No newline at end of file
diff --git a/docs/html/preview/notifications.jd b/docs/html/preview/notifications.jd
new file mode 100644
index 0000000..e0fb7be
--- /dev/null
+++ b/docs/html/preview/notifications.jd
@@ -0,0 +1,617 @@
+page.title=Design for Notifications
+page.tags="notifications","design","L"
+@jd:body
+
+<p>The notification system allows users to keep informed about relevant and timely
+events in your app, such as new chat messages from a friend or a calendar event.
+Think of notifications as a news channel that alerts the user to important events as
+they happen or a log that chronicles events while the user is not paying attention -
+and one that is synced as appropriate across all their Android devices.</p>
+
+<h4 id="New"><strong>New in L</strong></h4>
+
+<p>In L, notifications receive an important structural visual and functional update:</p>
+
+<ul>
+  <li> Visual changes to notifications as part of material design</li>
+  <li> Notifications are now available on the device lockscreen, yet sensitive content can still
+  be hidden behind it</li>
+   <li> A new presentation format called Heads-up for receiving high priority notifications while
+  using the device</li>
+  <li> Cloud-synced notifications - act on a notification on your Android tablet and it is also
+  dismissed on your phone.</li>
+  <li> And starting now (in Android 4.4W, API Level 20, the platform release for Android Wear),
+  your notifications will bridge to Android Wear devices. You can extend the functionality of
+  notifications on Wear in two different ways. First, you can add speech input and canned responses
+  to Actions on Wear, allowing users to complete tasks from their wrists. Second, you can write
+  Wear apps that hook into your notifications to go even further in terms of creating interactive
+  experiences for users.</li>
+</ul>
+
+<h2 id="Anatomy">Anatomy of a notification</h2>
+
+<p>This section goes over basic parts of a notification and how they can
+appear on different types of devices.</p>
+
+<h3 id="BaseLayout">Base Layout</h3>
+
+<p>At a minimum, all notifications consist of a base layout, including:</p>
+
+<ul>
+  <li> The notification's <strong>icon</strong>, symbolizing the originating app, and also potentially the kind of notification if the app has several substantially different sorts of notifications it can post</li>
+  <li> A notification <strong>title</strong> and additional <strong>text</strong></li>
+  <li> A <strong>timestamp</strong></li>
+</ul>
+
+<p>Notifications created with <code>Notification.Builder</code> for versions of Android earlier than L will look and work the same in L, with only minor stylistic changes that the system handles for you.</p>
+
+<div style="margin-top:20px" >
+  <div class="col-7">
+    <img src="{@docRoot}preview/images/notifications/Basic.png"
+      alt=""/>
+  </div>
+  <div class="col-4" style="padding-top:60px; text-align:center" >
+    <img src="{@docRoot}preview/images/notifications/WearBasic.png"
+      alt="" width="162" height="162" />
+  </div>
+</div>
+
+<div style="clear:both;">
+      <p class="img-caption">
+      Base layout of a handheld notification and the same notification on Wear,
+      with a user photo and a notification icon
+    </p>
+  </div>
+</div>
+
+<h3 id="ExpandedLayouts">Expanded layouts</h3>
+
+
+<p>You have the option to provide more details on notifications. You can use this to show the first few lines of a message or show a larger image preview. This provides the user with additional context, and - in some cases - may allow the user to read a message in its entirety. The user can pinch-zoom or perform a single-finger glide in order to toggle between compact and expanded layouts. For single event notifications, Android provides three expanded layout templates (text, inbox, and image) for you to re-use in your application. The following images show you how they look on handhelds and wearables.</p>
+
+<div class="col-5" style="margin-top:20px">
+  <img src="{@docRoot}preview/images/notifications/ExpandedText.png"
+    alt=""  />
+  <img style="margin-top:30px" src="{@docRoot}preview/images/notifications/Stack.png"
+    alt=""/>
+  <img style="margin-top:30px" src="{@docRoot}preview/images/notifications/ExpandedImage.png"
+    alt="" />
+</div>
+
+<div class="col-6" style="margin-top:20px">
+   <img style="margin-top:60px" src="{@docRoot}preview/images/notifications/Expanded.png"
+      alt="" />
+
+  <img style="margin-top:140px" src="{@docRoot}preview/images/notifications/figure6.png"
+    alt="" />
+</div>
+
+<h3 id="actions" style="clear:both">Actions</h3>
+
+<p>Android has supported optional actions that are displayed at the bottom of the notification, as far back as Jelly Bean. With actions, users can handle the most common tasks for a particular notification from within the notification shade without having to open the originating application. This speeds up interaction and, in conjunction with "swipe-to-dismiss", helps users to streamline their notification triaging experience.</p>
+
+
+<div class="col-6" style="margin-top:20px">
+  <img src="{@docRoot}preview/images/notifications/Action.png"
+    alt="" />
+  <p class="img-caption">
+   Calendar reminder notification with two actions
+  </p>
+</div>
+<div class="col-5" style="margin-top:20px">
+  <img src="{@docRoot}preview/images/notifications/ReplyAction.png" width="156px" height="156px"
+    alt="" />
+  <p class="img-caption">
+    Gmail new message notification - the actions appear to the right of the main card on Wear devices
+  </p>
+</div>
+
+
+<p style="clear:both">Be judicious with how many actions you include with a notification. The more actions you include, the more cognitive complexity you create. Limit yourself to the fewest number of actions possible by only including the most imminently important and meaningful ones.</p>
+
+<p>Good candidates for actions on notifications are actions that:</p>
+
+<ul>
+  <li> Are essential, frequent and typical for the content type you're displaying
+  <li> Allow the user to accomplish tasks quickly
+</ul>
+
+<p>Avoid actions that are:</p>
+
+<ul>
+  <li> Ambiguous
+  <li> Duplicative of the default action of the notification (such as "Read" or "Open")
+</ul>
+
+
+
+<p>You can specify a maximum of three actions, each consisting of an action icon and an action name. Adding actions to a simple base layout will make the notification expandable, even if the notification doesn't have an expanded layout. Since actions are only shown for expanded notifications and are otherwise hidden, you must make sure that any action a user can invoke from a notification is available from within the associated application as well.</p>
+
+<h2 id="notifications_on_android_wear">Notifications on Android Wear</h2>
+
+<p>Additionally, notifications and their actions are bridged over to Wear devices by default. Developers have control to control which notifications from bridging from the phone to the watch and vice versa. And developers can control which actions bridge as well. If your app includes actions that can't be accomplished with a single tap, either hide these actions on your Wear notification or consider hooking them up to a Wear app to allow the user to finish the action on their watch.</p>
+
+<div class="col-7">
+<h4>Bridging notifications</h4>
+
+<p><strong>Notifications that should be bridged</strong></p>
+
+<ul>
+  <li> New instant messages</li>
+</ul>
+
+<p><strong>Don't bridge</strong></p>
+
+<ul>
+  <li> If a podcasting app has new episodes available for download,
+  keep this notification on the phone.</li>
+</ul>
+</div>
+
+<div class="col-4" style="margin-top:20px">
+  <img src="{@docRoot}preview/images/notifications/WearBasic.png" width="156px" height="156px"
+    alt="" />
+</div>
+
+
+<div style="clear:left" class="col-7">
+<h4>Bridging actions</h4></p>
+
+<p><strong>Actions to bridge</strong></p>
+
+<ul>
+  <li> Single tap actions such as +1, Like, Heart</li>
+</ul>
+
+<p><strong>Actions not to bridge</strong></p>
+
+<ul>
+  <li> Actions that map to features that aren't possible on the watch</li>
+</ul>
+
+<p><b>Unique actions to define for Wear</b></p>
+
+<ul>
+  <li> Quick lists of canned responses such as "Be right back"</li>
+  <li> Open on phone</li>
+  <li> A "Comment" or "Reply" action that brings up the speech input screen</li>
+  <li> Actions that can launch Wear-specific apps</li>
+</ul>
+</div>
+
+<div class="col-4" style="margin-top:220px">
+  <img src="{@docRoot}preview/images/notifications/ReplyAction.png" width="156px" height="156px"
+    alt="" />
+</div>
+
+<h2 style="clear:left">Heads-up Notification</h2>
+<div class="figure" style="width:262px">
+  <img src="{@docRoot}preview/images/notifications/Headsup.png"
+    alt="" width="220" id="figure9" />
+  <p class="img-caption">
+    <strong>Figure 6.</strong> Example of a Heads-up notification (incoming phone call, high priority) coming in on top of an immersive app
+  </p>
+</div>
+
+<p>When notifications with priority set to High (see right) arrives, it is presented to users for a short period of time on the device with an expanded layout with its actions exposed.</p>
+<p> After this period of time, it retreats back to the Notification shade. If a notification is flagged as High or Max or a full-screen takeover, it gets a HUN in L.</p>
+
+<p><b>Good examples of Heads-up notifications</b></p>
+
+<ul>
+  <li> Incoming phone call when using device</li>
+  <li> Alarm when using device</li>
+  <li> New SMS message</li>
+  <li> Low battery</li>
+</ul>
+
+<h2 style="clear:both" id="guidelines">Guidelines</h2>
+
+
+<div class="figure" style="width:366px; margin-top:40px">
+  <img src="{@docRoot}preview/images/notifications/Triggered.png"
+    alt="" width="366" height="142" />
+  <p class="img-caption">
+    Notification that shows the person who triggered it and the content they are sending you
+  </p>
+</div>
+
+<h3 id="MakeItPersonal">Make it personal</h3>
+
+<p>For notifications of items sent by another person (such as a message or status update), include that person's image using setLargeIcon. Also attach information about the person to the notification's metadata (see EXTRA_PEOPLE).</p>
+
+<p>Your notification's main icon will still be shown, so the user can associate it with the icon visible in the status bar.</p>
+
+<h3 id="navigate_to_the_right_place">Navigate to the right place</h3>
+
+<p>When the user touches the body of a notification (outside of the action buttons), open your app to the place where the user can view and act upon the data referenced in the notification. In most cases this will be the detail view of a single data item such as a message, but it might also be a summary view if the notification is stacked (see <em>Stacked notifications</em> below) and references multiple items. If in any of those cases the user is taken to a hierarchy level below your app's top-level, insert navigation into your app's back stack to allow them to navigate to your app's top level using the system back button. For more information, see the chapter on <em>System-to-app navigation</em> in the <a href="/design/patterns/navigation.html">Navigation</a> design pattern.</p>
+
+<h3 id="correctly_set_and_manage_notification_priority">Correctly set and manage notification priority</h3>
+
+<p>Starting with Jelly Bean, Android supported a priority flag for notifications. It allows you to influence where your notification will appear in comparison to other notifications and help to make sure that users always see their most important notifications first. You can choose from the following priority levels when posting a notification:</p>
+<table>
+ <tr>
+    <td class="tab0">
+<p><strong>Priority</strong></p>
+</td>
+    <td class="tab0">
+<p><strong>Use</strong></p>
+</td>
+ </tr>
+ <tr>
+    <td class="tab1">
+<p><code>MAX</code></p>
+</td>
+    <td class="tab1">
+<p>Use for critical and urgent notifications that alert the user to a condition that is time-critical or needs to be resolved before they can continue with a particular task.</p>
+</td>
+ </tr>
+ <tr>
+    <td class="tab1">
+<p><code>HIGH</code></p>
+</td>
+    <td class="tab1">
+<p>Use high priority notifications primarily for important communication, such as message or chat events with content that is particularly interesting for the user. High priority notifications will get the Heads-Up Notification display starting in L.</p>
+</td>
+ </tr>
+ <tr>
+    <td class="tab1">
+<p><code>DEFAULT</code></p>
+</td>
+    <td class="tab1">
+<p>The default priority. Keep all notifications that don't fall into any of the other categories at this priority level.</p>
+</td>
+ </tr>
+ <tr>
+    <td class="tab1">
+<p><code>LOW</code></p>
+</td>
+    <td class="tab1">
+<p>Use for notifications that you still want the user to be informed about, but that rate low in urgency. LOW notifications will tend to show up at the bottom of the list, which makes them a good choice for things like pubic/undirected social updates: the user has asked to be notified about them, but they should never take precedence over urgent or direct communication.</p>
+</td>
+ </tr>
+ <tr>
+    <td class="tab1">
+<p><code>MIN</code></p>
+</td>
+    <td class="tab1">
+<p>Contextual/background information (e.g. weather information, contextual location information). Minimum priority notifications will not show in the status bar. The user will only discover them when they expand the notification shade.</p>
+</td>
+ </tr>
+</table>
+
+
+<h4 id="how_to_choose_an_appropriate_priority"><strong>How to choose an appropriate
+priority</strong></h4>
+
+<p>Default, High, and Max priority are interruptive priority levels and risk interrupting the user from what they are doing. This should not not be taken lightly, so these levels should be  reserved for notifications that:</p>
+
+<ul>
+  <li> Involve another person</li>
+  <li> Are time-sensitive</li>
+  <li> Might immediately change the user's behavior in the real world</li>
+</ul>
+
+<p>Notifications set to Low and Min can still be very valuable for the user. Many if not most notifications just don't need to command the user's immediate attention, or vibrate the user's wrist, yet contain information that they will find valuable when they choose to look for notifications. Criteria for Low and Min priority notifications:</p>
+
+<ul>
+  <li> Don't involve other people</li>
+  <li> Aren't time sensitive</li>
+  <li> Is content the user might be interested in but could choose to browse at their leisure</li>
+</ul>
+
+
+  <img src="{@docRoot}preview/images/notifications/notifications_pattern_priority.png"
+    alt="" width="700"/>
+
+
+<h3 style="clear:both" id="set_a_notification_category">Set a notification category</h3>
+
+<p>If your notification falls into one of the predefined categories (see below), assign it accordingly.  Aspects of the system UI such as the notification shade (or any other notification listener) may use this information to make ranking and filtering decisions.</p>
+<table>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_CALL</code></p>
+</td>
+    <td>
+<p>Incoming call (voice or video) or similar synchronous communication request</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_MESSAGE</code></p>
+</td>
+    <td>
+<p>Incoming direct message (SMS, instant message, etc.)</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_EMAIL</code></p>
+</td>
+    <td>
+<p>Asynchronous bulk message (email)</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_EVENT</code></p>
+</td>
+    <td>
+<p>Calendar event</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_PROMO</code></p>
+</td>
+    <td>
+<p>Promotion or advertisement</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_ALARM</code></p>
+</td>
+    <td>
+<p>Alarm or timer</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_PROGRESS</code></p>
+</td>
+    <td>
+<p>Progress of a long-running background operation</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_SOCIAL</code></p>
+</td>
+    <td>
+<p>Social network or sharing update</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_ERROR</code></p>
+</td>
+    <td>
+<p>Error in background operation or authentication status</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_TRANSPORT</code></p>
+</td>
+    <td>
+<p>Media transport control for playback</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_SYSTEM</code></p>
+</td>
+    <td>
+<p>System or device status update.  Reserved for system use.</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_SERVICE</code></p>
+</td>
+    <td>
+<p>Indication of running background service</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_RECOMMENDATION</code></p>
+</td>
+    <td>
+<p>A specific, timely recommendation for a single thing.  For example, a news app might want to recommend a news story it believes the user will want to read next.</p>
+</td>
+ </tr>
+ <tr>
+    <td>
+<p><code>Notification.CATEGORY_STATUS</code></p>
+</td>
+    <td>
+<p>Ongoing information about device or contextual status</p>
+</td>
+ </tr>
+</table>
+
+
+<p> </p>
+
+<h3 id="summarize_your_notifications">Summarize your notifications</h3>
+
+<p>If your app creates a notification while another of the same type is still pending, avoid creating an altogether new notification object. Instead, turn it into a summary notification for the app.</p>
+
+<p>A summary notification builds a summary description and allows the user to understand how many notifications of a particular kind are pending.</p>
+
+<div class="col-6">
+<p><strong>Don't</strong></p>
+
+  <img src="{@docRoot}preview/images/notifications/Summarise_Dont.png"
+    alt="" width="300" />
+
+</div>
+
+<div class="col-5">
+<p><strong>Do</strong></p>
+
+  <img src="{@docRoot}preview/images/notifications/Summarise_Do.png"
+    alt="" width="300"/>
+</div>
+</div>
+
+
+<p style="clear:left">You can provide more detail about the individual notifications that make up a  summary by using the expanded digest layout. This allows users to gain a better sense of which notifications are pending and if they are interesting enough to be read in detail within the associated app.</p>
+
+  <img src="{@docRoot}preview/images/notifications/Stack.png" style="margin-bottom:30px"
+    alt="" width="370" />
+  <p class="img-caption">
+  Expanded and contracted notification that is a summary (using InboxStyle)
+  </p>
+
+
+<h3 style="clear:right" id="make_notifications_optional">Make notifications optional</h3>
+
+<p>Users should always be in control of notifications. Allow the user to diszable your app's
+notifications or change their alert properties, such as alert sound and whether to use vibration,
+by adding a notification settings item to your application settings.</p>
+
+<h3 id="use_distinct_icons">Use distinct icons</h3>
+<p>By glancing at the notification area, the user should be able to discern what kinds of
+notifications are currently pending.</p>
+
+<div class="figure">
+  <img src="{@docRoot}preview/images/notifications/ProductIcons.png"
+    alt="" width="420" />
+</div>
+
+  <div><p><strong>Do</strong></p>
+    <p>Look at the notification icons Android apps already provide and create notification icons for
+    your app that are sufficiently distinct in appearance.</p>
+
+    <p><strong>Do</strong></p>
+    <p>Use the proper <a href="/design/style/iconography.html#notification">notification icon
+    style</a> for small icons, and the Material Light
+    <a href="/design/style/iconography.html#action-bar">action bar icon style</a> for your action
+    icons. Do not place any additional alpha (dimming or fading) into your small icons and action
+    icons; they can have anti-aliased edges, but because L uses these icons as masks (that is, only
+    the alpha channel is used), the image should generally be drawn at full opacity.</p>
+<p ><strong>Do</strong></p>
+<p >Keep your icons visually simple and avoid excessive detail that is hard to discern.</p>
+
+</div>
+<p style="clear:both"><strong>Don't</strong></p>
+
+<p>Use color to distinguish your app from others. Notification icons should only be a white-on-transparent background image.</p>
+
+
+<h3 id="pulse_the_notification_led_appropriately">Pulse the notification LED appropriately</h3>
+
+<p>Many Android devices contain a notification LED, which is used to keep the user informed about events while the screen is off. Notifications with a priority level of MAX, HIGH, or DEFAULT should cause the LED to glow, while those with lower priority (LOW and MIN) should not.</p>
+
+<p>The user's control over notifications should extend to the LED. When you use DEFAULT_LIGHTS, the LED will glow with a white color. Your notifications shouldn't use a different color unless the user has explicitly customized it.</p>
+
+<h2 id="building_notifications_that_users_care_about">Building notifications that users care about</h2>
+
+<p>To create an app that users love, it is important to design your notifications carefully.  Notifications embody your app's voice, and contribute to your app's personality. Unwanted or unimportant notifications can annoy the user or make them resent how much attention the app wants from them, so use notifications judiciously.</p>
+
+<h3 id="when_to_display_a_notification">When to display a notification</h3>
+<div class="figure">
+  <img src="{@docRoot}preview/images/notifications/TimeSensitive.png"
+    alt="" width="360" />
+  <p class="img-caption">
+   Time sensitive notification examples
+  </p>
+</div>
+<p>To create an application that people enjoy using, it's important to recognize that the user's attention and focus is a resource that must be protected. While Android's notification system has been designed to minimize the impact of notifications on the user's attention, it is nonetheless still important to be aware of the fact that notifications are interrupting the user's task flow. As you plan your notifications, ask yourself if they are important enough to warrant an interruption. If you are unsure, allow the user to opt into a notification using your apps notification settings or adjust the notifications priority flag to Low or Min to avoid distracting the user while they are doing something else.</p>
+
+<p>While well behaved apps generally only speak when spoken to, there are some limited cases where an app actually should interrupt the user with an unprompted notification.</p>
+
+<p>Notifications should be used primarily for <strong>time sensitive events</strong>, and especially if these synchronous events <strong>involve other people</strong>. For instance, an incoming chat is a real time and synchronous form of communication: there is another user actively waiting on you to respond. Calendar events are another good example of when to use a notification and grab the user's attention, because the event is imminent, and calendar events often involve other people.</p>
+
+<h3 style="clear:both" id="when_not_to_display_a_notification">When not to display a notification</h3>
+
+<div class="figure" style="margin-top:60px">
+  <img src="{@docRoot}preview/images/notifications/AntiSample1.png"
+    alt="" width="280px" />
+</div>
+
+<p>There are however many other cases where notifications should not be used:</p>
+
+<ul>
+  <li> Avoid notifying the user of information that is not directed specifically at them, or information that is not truly time sensitive. For instance the asynchronous and undirected updates flowing through a social network generally do not warrant a real time interruption. For the users that do care about them, allow them to opt-in.
+  <li> Don't create a notification if the relevant new information is currently on screen. Instead, use the UI of the application itself to notify the user of new information directly in context. For instance, a chat application should not create system notifications while the user is actively chatting with another user.
+  <li> Don't interrupt the user for low level technical operations, like saving or syncing information, or updating an application, if it is possible for the system to simply take care of itself without involving the user.
+  <li> Don't interrupt the user to inform them of an error if it is possible for the application to recover from the error on its own without the user taking any action.
+  <li> Don't create notifications that have no true notification content and merely  advertise your app. A notification should provide useful, timely, new information and should not be used to merely launch an app.
+  <li> Don't create superfluous notifications just to get your brand in front of users. Such notifications will only frustrate and likely alienate your audience. The best way to provide the user with a small amount of updated information and to keep them engaged with your application is to develop a widget that they can choose to place on their home screen.
+</ul>
+
+<h2 style="clear:left" id="interacting_with_notifications">Interacting With Notifications</h2>
+
+<p>Notifications are indicated by icons in the status bar and can be accessed by opening the notification drawer.</p>
+
+<p>Touching a notification opens the associated app to detailed content matching the notification. Swiping left or right on a notification removes it from the list.</p>
+
+<h3 id="ongoing_notifications">Ongoing notifications</h3>
+<div class="figure" style="width:337px">
+  <img src="{@docRoot}preview/images/notifications/MusicPlayback.png"
+    alt="" width="337"  />
+      <p class="img-caption">
+    Ongoing notification due to music playback
+  </p>
+</div>
+<p>Ongoing notifications keep users informed about an ongoing process in the background. For example, music players announce the currently playing track in the notification system and continue to do so until the user stops the playback. They can also be used to show the user feedback for longer tasks like downloading a file, or encoding a video. Ongoing notifications cannot be manually removed from the notification drawer.</p>
+
+<p>The L lockscreen doesn't show transport controls for RCC (RemoteControlClient)s anymore. But the lockscreen <em>does</em> show notifications, so each app's playback notification is now the primary way for users to control playback from a locked state. This gives apps more control over which buttons to show and in what way, while providing a consistent experience for the user whether on the lockscreen or unlocked.</p>
+
+<h3 style="clear:both" id="dialogs_and_toasts_are_for_feedback_not_notification">Dialogs
+and toasts are for feedback not notifications</h3>
+
+<p>Your app should not create a dialog or toast if it is not currently on screen. Dialogs and Toasts should only be displayed as the immediate response to the user taking an action inside of your app. For further guidance on the use of dialogs and toasts, refer to <a href="/design/patterns/confirming-acknowledging.html">Confirming & Acknowledging</a>.</p>
+
+<h3>Ranking and Ordering</h3>
+
+<p>Notifications are "news" and so they are essentially shown in reverse-chronological order, with special consideration given to the app's stated notification priority.</p>
+
+<p>In L, notifications are now a key part of the lockscreen, and are featured prominently every time the device display comes on. Because space on the lockscreen is tight, it is more important than ever to identify the most urgent or relevant notifications.</p>
+
+<p>Therefore, L has a more sophisticated sorting algorithm for notifications, taking into account:</p>
+
+<ul>
+  <li> The timestamp and application's stated priority, as before.
+  <li> Whether the notification has recently disturbed the user with sound or vibration. (That is, if the phone just made noise, and the user wants to know "what just happened?" the lockscreen should answer that at a glance.)
+  <li> Any people that are attached to the notification using <code>EXTRA_PEOPLE</code>, and in particular whether those are starred contacts.
+</ul>
+
+<p>To best take advantage of this sorting, developers should focus on the user experience they want to create rather than aiming for any particular spot on the list.</p>
+
+  <img src="{@docRoot}preview/images/notifications/AntiSample3.png"
+    alt="" width="700px" />
+
+  <p class="img-caption" style="margin-top:20px">Gmail notifications are default priority, so they normally sort below messages from an instant messaging app like Hangouts, but Gmail will get a temporary bump when new messages come in.
+  </p>
+
+
+<h3>On the lockscreen</h3>
+
+<p>Starting in L, notifications are visible on the lockscreen, and so we must consider the user's privacy. Notifications often contain sensitive information, and we must take care when showing it to anyone who picks up the device and turns on the display.</p>
+
+<ul>
+  <li> For devices without a secure lockscreen, a simple slide gesture unlocks the whole device. Therefore, Android will always show the complete contents of all notifications on insecure lockscreens.
+  <li> When a device has a secure lockscreen (PIN, pattern, or password), however, it divides the interface into two spheres: "public", the things that are displayed atop a secure lockscreen and can therefore be seen by anyone; and "private", the world behind that lockscreen, which can only be accessed by supplying the correct authentication.
+</ul>
+
+
+
+<h3>The user decides what shows on the secure lockscreen</h3>
+<div class="figure" style="width:249px">
+  <img src="{@docRoot}preview/images/notifications/LockScreen.png"
+    alt="" width="249" height="482" id="figure22" />
+      <p class="img-caption">
+    Notifications on the lockscreen followed by the Pattern Unlock when the user attempts to unlock the phone.
+  </p>
+</div>
+
+<p>When setting up a secure lockscreen, the user can choose to conceal sensitive details from atop the secure lockscreen. In this case the SystemUI considers the notification's <em>visibility level</em> to figure out what can safely be shown.</p>
+
+<p>To control the visibility level, call <code>android.app.Notification.Builder.setVisibility()</code> and specify one of these values:</p>
+
+<ul>
+  <li><code>android.app.Notification.VISIBILITY_PUBLIC</code>. Shows the notification's full content. This is the system default if visibility is left unspecified.
+  <li><code>android.app.Notification.VISIBILITY_PRIVATE</code>. The lockscreen will reveal basic information about the existence of this notification, including its icon and the name of the app that posted it. The rest of the notification's details, however, are not displayed.
+  <ul>
+    <li> If you want to provide a different public version of your notification for the system to display on a secure lockscreen, supply a replacement Notification object in the <code>android.app.Notification.publicVersion</code> field.
+    <li> This is an app's opportunity to create a redacted version of the content that is still useful but does not reveal personal information.
+    <li> <strong>Example: </strong>An SMS app whose notifications include the text of the SMS and the sender's name and contact icon. This notification should be <code>VISIBILITY_PRIVATE</code>, but the <code>publicVersion</code> could still contain useful information like "3 new messages" without any other identifying details.
+  </ul>
+  <li><code>android.app.Notification.VISIBILITY_SECRET</code>. Shows only the most minimal information, excluding even the notification's icon.
+</ul>
\ No newline at end of file
diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs
new file mode 100644
index 0000000..a505905
--- /dev/null
+++ b/docs/html/preview/preview_toc.cs
@@ -0,0 +1,107 @@
+<ul id="nav">
+
+
+  <li class="nav-section">
+    <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/setup-sdk.html">Set up the SDK
+      </a></div>
+  </li>
+  <li class="nav-section">
+    <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/api-overview.html">API Overview
+      </a></div>
+  </li>
+  <li class="nav-section">
+
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>preview/material/index.html">Material Design
+      </a></div>
+    <ul>
+      <li><a href="<?cs var:toroot ?>preview/material/get-started.html">Get Started</a></li>
+      <li><a href="<?cs var:toroot ?>preview/material/theme.html">Material Theme</a></li>
+      <li><a href="<?cs var:toroot ?>preview/material/ui-widgets.html">UI Widgets</a></li>
+      <li><a href="<?cs var:toroot ?>preview/material/views-shadows.html">Views and Shadows</a></li>
+      <li><a href="<?cs var:toroot ?>preview/material/animations.html">Animations</a></li>
+      <li><a href="<?cs var:toroot ?>preview/material/compatibility.html">Compatibility</a></li>
+    </ul>
+  </li>
+
+  <li class="nav-section">
+    <div class="nav-section-header empty">
+    <a href="<?cs var:toroot ?>preview/notifications.html">Notification Design</a></div>
+  </li>
+
+
+  <li class="nav-section">
+    <div class="nav-section-header">
+      <a href="<?cs var:toroot ?>preview/tv/index.html">TV</a>
+      </div>
+    <ul>
+      <li><a href="<?cs var:toroot ?>preview/tv/start/index.html">
+        Get Started</a></li>
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>preview/tv/design/index.html">
+          Design</a></div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>preview/tv/design/principles.html">
+            Creative Vision</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/design/patterns.html">
+            UI Patterns</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/design/style.html">
+            Style</a></li>
+        </ul>
+      </li>
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>preview/tv/ui/index.html">
+          User Interface</a></div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/layouts.html">
+            Layouts</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/navigation.html">
+            Navigation</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/browse.html">
+            BrowseFragment</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/details.html">
+            DetailsFragment</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/in-app-search.html">
+            In-App Search</a></li>
+          <li><a href="<?cs var:toroot ?>preview/tv/ui/recommendations.html">
+            Recommendations</a></li>
+        </ul>
+      </li>
+      <li><a href="<?cs var:toroot ?>preview/tv/games/index.html">
+        Games on TV</a></li>
+      <li><a href="<?cs var:toroot ?>preview/tv/start/hardware-features.html">
+        Hardware Features</a></li>
+      <li><a href="<?cs var:toroot ?>preview/tv/adt-1/index.html">
+        ADT-1</a></li>
+    </ul>
+  </li>
+
+  <li class="nav-section">
+    <div class="nav-section-header empty">
+      <a href="<?cs var:toroot ?>preview/samples.html">Samples</a>
+      </div>
+  </li>
+  <li class="nav-section">
+    <div class="nav-section-header empty">
+
+      <a href="<?cs var:toroot ?>preview/reference.html">Reference</a>
+
+    </div>
+  </li>
+    <li class="nav-section">
+    <div class="nav-section-header empty">
+      <a href="<?cs var:toroot ?>preview/support.html">Support</a>
+      </div>
+  </li>
+  <li class="nav-section">
+    <div class="nav-section-header empty">
+      <a href="<?cs var:toroot ?>preview/license.html">License Agreement</a>
+      </div>
+  </li>
+  <li class="nav-section" style="margin: 20px 0 0 -10px;">
+    <div class="nav-section-header empty">
+      <a href="<?cs var:toroot ?>index.html" class="back-link">Developer Home</a>
+      </div>
+  </li>
+</ul>
diff --git a/docs/html/preview/reference.jd b/docs/html/preview/reference.jd
new file mode 100644
index 0000000..b70e4e5
--- /dev/null
+++ b/docs/html/preview/reference.jd
@@ -0,0 +1,11 @@
+page.title=Reference
+
+@jd:body
+
+<p>The reference documentation and API difference report are available in this downloadable package.
+</p>
+
+<ul>
+  <li><a href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip">L
+  Developer Preview reference</a></li>
+</ul>
\ No newline at end of file
diff --git a/docs/html/preview/samples.jd b/docs/html/preview/samples.jd
new file mode 100644
index 0000000..155ae21
--- /dev/null
+++ b/docs/html/preview/samples.jd
@@ -0,0 +1,237 @@
+page.title=Samples
+
+@jd:body
+
+<p>The following code samples are provided for the L Developer Preview. You can
+download them in the Android SDK Manager under the <b>SDK Samples</b> component
+for the L Developer Preview.</p>
+
+<p class="note">
+  <strong>Note:</strong> At this time, the downloadable projects are designed
+   for use with Gradle and Android Studio.
+</p>
+
+
+<h3 id="BasicManagedProfile">BasicManagedProfile</h3>
+<div class="figure" style="width:220px">
+  <img src="{@docRoot}preview/images/BasicManagedProfile.png"
+     srcset="{@docRoot}preview/images/BasicManagedProfile@2x.png 2x"
+     alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure 1.</strong> The BasicManagedProfile sample app.
+  </p>
+</div>
+
+<p>This sample demonstrates how to create a managed profile. You can also:</p>
+<ul>
+  <li>Enable or disable other apps, and set restrictions on them.</li>
+  <li>Configure intents to be forwarded between the primary account and the
+   managed profile.</li>
+  <li>Wipe all the data associated with the managed profile.</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> There can be only one managed profile on
+  a device at a time.</p>
+
+<p><a href="http://github.com/googlesamples/android-BasicManagedProfile">Get it on GitHub</a></p>
+
+<h3 id="Camera2Basic">Camera2Basic</h3>
+
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>This sample demonstrates the basic use of the Camera2 API. The sample code
+demonstrates how you can display camera preview and take pictures.</p>
+
+<p><a href="http://github.com/googlesamples/android-Camera2Basic">Get it on GitHub</a></p>
+
+
+<h3 id="Camera2Video">Camera2Video</h3>
+<!--
+<div class="figure" style="width:220px">
+<img src="" srcset="@2x.png 2x" alt="" height="375" />
+    <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>This sample demonstrates how to record video using the Camera2 API.</p>
+
+<p><a href="http://github.com/googlesamples/android-Camera2Video">Get it on GitHub</a></p>
+
+<h3 id="ActivitySceneTransitionBasic">ActivitySceneTransitionBasic</h3>
+<div class="figure" style="width:220px">
+  <img src="{@docRoot}preview/images/ActivitySceneTransitionBasic.png"
+      srcset="{@docRoot}preview/images/ActivitySceneTransitionBasic@2x.png 2x"
+      alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure 2.</strong> The ActivitySceneTransitionBasic sample app.
+  </p>
+  </div>
+
+<p> This sample demonstrates how to the use {@link android.app.Activity} scene
+transitions when transitioning from one activity to another. Uses a combination
+of <code>moveImage</code> and <code>changeBounds</code> to nicely transition
+from a grid of images to an activity with a large image and detail text. </p>
+
+<p><a href="http://github.com/googlesamples/android-ActivitySceneTransition">Get it on GitHub</a></p>
+
+<h3 id="ElevationBasic">ElevationBasic</h3>
+<!--
+<div class="figure" style="width:220px">
+<img src="" srcset="@2x.png 2x" alt="" height="375" />
+    <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample demonstrates two alternative ways to move a view in the z-axis:</p>
+
+<ul>
+  <li>With a fixed elevation, using XML.</li>
+  <li>Raising the elevation when the user taps on it, using
+      <code>setTranslationZ()</code>.</li>
+</ul>
+
+<p><a href="http://github.com/googlesamples/android-ElevationBasic">Get it on GitHub</a></p>
+
+<h3 id="ElevationDrag">ElevationDrag</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>This sample demonstrates a drag and drop action on different shapes.
+Elevation and z-translation are used to render the shadows. The views are
+clipped using different outlines.</p>
+
+<p><a href="http://github.com/googlesamples/android-ElevationDrag">Get it on GitHub</a></p>
+
+
+<h3 id="ClippingBasic">ClippingBasic</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample demonstrates clipping on a {@link android.view.View}.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-ClippingBasic">Get it on GitHub</a></p>
+
+<div class="figure" style="width:220px">
+  <img src="{@docRoot}preview/images/JobSchedulerSample.png"
+      srcset="{@docRoot}preview/images/JobSchedulerSample@2x.png 2x"
+      alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure 3.</strong> The JobSchedulerSample sample app.
+  </p>
+</div>
+
+<h3 id="GameControllerSample">GameControllerSample</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample implements a multi-player game, demonstrating game controller input
+handling.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-GameController">Get it on GitHub</a></p>
+
+
+<h3 id="Visual-Game-Controller">Visual-Game-Controller</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample displays events received from a game controller shown on the screen.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-VisualGameController">Get it on GitHub</a></p>
+
+<h3 id="AndroidTVLeanbackSample">AndroidTVLeanbackSample</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample demonstrates use of the Android TV Leanback Support Library.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-Leanback">Get it on GitHub</a></p>
+
+<h3 id="JobSchedulerSample">JobSchedulerSample</h3>
+
+<p>
+This sample app allows the user to schedule jobs through the UI, and shows
+visual cues when the jobs are executed.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-JobScheduler">Get it on GitHub</a></p>
+
+<h3 id="NavigationDrawerSample">NavigationDrawerSample</h3>
+<!--
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+-->
+
+<p>
+This sample illustrates a common usage of the Android support library's
+{@link android.support.v4.widget.DrawerLayout} widget.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-NavigationDrawer">Get it on GitHub</a></p>
+<!--
+<h3 id="">SampleName</h3>
+
+<div class="figure" style="width:220px">
+  <img src="" srcset="@2x.png 2x" alt="" height="375" />
+  <p class="img-caption">
+    <strong>Figure n.</strong> Single sentence summarizing the figure.
+  </p>
+</div>
+
+<p>
+**description**
+</p>
+-->
diff --git a/docs/html/preview/setup-sdk.jd b/docs/html/preview/setup-sdk.jd
new file mode 100644
index 0000000..af466ab
--- /dev/null
+++ b/docs/html/preview/setup-sdk.jd
@@ -0,0 +1,364 @@
+page.title=Setting Up the Preview SDK
+@jd:body
+
+
+
+
+
+
+
+<div style="position:relative; min-height:600px">
+
+  <div class="wrap" id="tos" style="position:absolute;display:none;width:inherit;">
+
+    <p class="sdk-terms-intro">Before installing the Android SDK, you must agree to the following terms and conditions.</p>
+
+      <h2 class="norule">Terms and Conditions</h2>
+    <div class="sdk-terms" onfocus="this.blur()" style="width:678px">
+This is the Android SDK Preview License Agreement (the “License Agreement”).
+
+1. Introduction
+
+1.1 The Android SDK Preview (referred to in the License Agreement as the “Preview” and specifically including the Android system files, packaged APIs, and Preview library files, if and when they are made available) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the Preview.
+
+1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
+
+1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
+
+2. Accepting the License Agreement
+
+2.1 In order to use the Preview, you must first agree to the License Agreement. You may not use the Preview if you do not accept the License Agreement.
+
+2.2 By clicking to accept and/or using the Preview, you hereby agree to the terms of the License Agreement.
+
+2.3 You may not use the Preview and may not accept the License Agreement if you are a person barred from receiving the Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Preview.
+
+2.4 If you will use the Preview internally within your company or organization you agree to be bound by the License Agreement on behalf of your employer or other entity, and you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Preview on behalf of your employer or other entity.
+
+3. Preview License from Google
+
+3.1 Subject to the terms of the License Agreement, Google grants you a royalty-free, non-assignable, non-exclusive, non-sublicensable, limited, revocable license to use the Preview, personally or internally within your company or organization, solely to develop applications to run on the Android platform.
+
+3.2 You agree that Google or third parties owns all legal right, title and interest in and to the Preview, including any Intellectual Property Rights that subsist in the Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
+
+3.3 You may not use the Preview for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Preview or any part of the Preview; or (b) load any part of the Preview onto a mobile handset or any other hardware device except a personal computer, combine any part of the Preview with other software, or distribute any software or device incorporating a part of the Preview.
+
+3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Preview.
+
+3.5 Use, reproduction and distribution of components of the Preview licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights.
+
+3.6 You agree that the form and nature of the Preview that Google provides may change without prior notice to you and that future versions of the Preview may be incompatible with applications developed on previous versions of the Preview. You agree that Google may stop (permanently or temporarily) providing the Preview (or any features within the Preview) to you or to users generally at Google's sole discretion, without prior notice to you.
+
+3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
+
+3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Preview.
+
+4. Use of the Preview by You
+
+4.1 Google agrees that nothing in the License Agreement gives Google any right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the Preview, including any intellectual property rights that subsist in those applications.
+
+4.2 You agree to use the Preview and write applications only for purposes that are permitted by (a) the License Agreement, and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
+
+4.3 You agree that if you use the Preview to develop applications, you will protect the privacy and legal rights of users. If users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If users provide you with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, each user has given you permission to do so.
+
+4.4 You agree that you will not engage in any activity with the Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of Google or any third party.
+
+4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
+
+4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
+
+4.7 The Preview is in development, and your testing and feedback are an important part of the development process. By using the Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Preview having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Preview as this Preview will no longer be supported after the official Android SDK is released.
+
+5. Your Developer Credentials
+
+5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
+
+6. Privacy and Information
+
+6.1 In order to continually innovate and improve the Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Preview are being used and how they are being used. Before any of this information is collected, the Preview will notify you and seek your consent. If you withhold consent, the information will not be collected.
+
+6.2 The data collected is examined in the aggregate to improve the Preview and is maintained in accordance with Google's Privacy Policy located at http://www.google.com/policies/privacy/.
+
+7. Third Party Applications
+
+7.1 If you use the Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
+
+7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
+
+7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party.
+
+8. Using Google APIs
+
+8.1 Google APIs
+
+8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
+
+8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
+
+9. Terminating the License Agreement
+
+9.1 the License Agreement will continue to apply until terminated by either you or Google as set out below.
+
+9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the Preview and any relevant developer credentials.
+
+9.3 Google may at any time, terminate the License Agreement, with or without cause, upon notice to you.
+
+9.4 The License Agreement will automatically terminate without notice or other action upon the earlier of:
+(A) when Google ceases to provide the Preview or certain parts of the Preview to users in the country in which you are resident or from which you use the service; and
+(B) Google issues a final release version of the Android SDK.
+
+9.5 When the License Agreement is terminated, the license granted to you in the License Agreement will terminate, you will immediately cease all use of the Preview, and the provisions of paragraphs 10, 11, 12 and 14 shall survive indefinitely.
+
+10. DISCLAIMERS
+
+10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE PREVIEW IS AT YOUR SOLE RISK AND THAT THE PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
+
+10.2 YOUR USE OF THE PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. WITHOUT LIMITING THE FOREGOING, YOU UNDERSTAND THAT THE PREVIEW IS NOT A STABLE RELEASE AND MAY CONTAIN ERRORS, DEFECTS AND SECURITY VULNERABILITIES THAT CAN RESULT IN SIGNIFICANT DAMAGE, INCLUDING THE COMPLETE, IRRECOVERABLE LOSS OF USE OF YOUR COMPUTER SYSTEM OR OTHER DEVICE.
+
+10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+
+11. LIMITATION OF LIABILITY
+
+11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
+
+12. Indemnification
+
+12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Preview, (b) any application you develop on the Preview that infringes any Intellectual Property Rights of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you of the License Agreement.
+
+13. Changes to the License Agreement
+
+13.1 Google may make changes to the License Agreement as it distributes new versions of the Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Preview is made available.
+
+14. General Legal Terms
+
+14.1 the License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Preview.
+
+14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
+
+14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable.
+
+14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement.
+
+14.5 EXPORT RESTRICTIONS. THE PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
+
+14.6 The License Agreement may not be assigned or transferred by you without the prior written approval of Google, and any attempted assignment without such approval will be void. You shall not delegate your responsibilities or obligations under the License Agreement without the prior written approval of Google.
+
+14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
+    </div><!-- sdk terms -->
+
+
+
+    <div id="sdk-terms-form">
+      <p>
+        <input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
+        <label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
+      </p>
+      <p><a href="" class="button disabled" id="downloadForRealz" onclick="return onDownloadForRealz(this);"></a></p>
+    </div>
+
+
+  </div><!-- end TOS -->
+
+
+
+
+
+
+
+
+
+  <div id="landing">
+
+<p>The Preview SDK is available from the Android SDK Manager. <!-- Not yet! -->
+This document assumes that you are familiar with Android app development, such
+as using the Android SDK Manager and creating projects. If you're new to
+Android, see <a href="/training/basics/firstapp/index.html">Building Your First
+App</a> training lesson first.</a></p>
+
+<h2 id="downloadSdk">Download the SDK</h2>
+
+<ol>
+  <li>Start the Android SDK Manager.</li>
+  <li>In the <b>Tools</b> section, select the latest Android <b>SDK Tools</b>,
+    <b>Platform-tools</b>, and <b>Build-tools</b>.</li>
+    <!-- Android L not yet showing up in Android SDK Manager...  -->
+  <li>Select everything under the <b>Android L Developer Preview</b> section and
+    click <b>Install packages...</b></li>
+  <li>Accept the Licensing Agreement for all of the packages and click
+    <b>Install</b>.</li>
+</ol>
+
+<p class="note"><strong>Note:</strong> The Eclipse ADT plug-in requires Java 7
+if your compilation target is the L developer preview.</p>
+
+<h2 id="setupHardware">Set Up Hardware and AVDs</h2>
+
+<p>The Android L developer preview provides you with 32-bit system images
+to flash the following devices:
+</p>
+
+<ul>
+  <li>Nexus 5</li>
+  <li>Nexus 7 Wi-Fi (version 2, released in 2013)</li>
+</ul>
+
+<p>In addition, you also get the emulator system images, which includes
+experimental 64-bit system images along with standard 32-bit system images.
+</p>
+
+<h3 id="installImage">Install the L Preview System Image</h3>
+
+<p class="warning"><b>Warning:</b> This is a preview version of the Android
+system image, and is subject to change. Your use of this system image is
+governed by the Android SDK Preview License Agreement. The Android preview
+system image is not a stable release, and may contain errors and defects that
+can result in damage to your computer systems, devices, and data. The preview
+Android system image is not subject to the same testing as the factory OS and
+can cause your phone and installed services and applications to stop working.
+</p>
+
+
+<ol>
+  <li>Download and uncompress the Android Developer Preview package.
+    <table style="width:860px">
+      <tr>
+        <th scope="col">Device</th>
+        <th scope="col">Download</th>
+        <th scope="col">Checksum</th>
+      </tr>
+      <tr id="hammerhead">
+        <td>Nexus 5 (GSM/LTE) <br>"hammerhead"</td>
+        <td><a href="#top" onclick="onDownload(this)"
+          >hammerhead-lpv79-preview-ac1d8a8e.tgz</a></td>
+        <td>MD5: <code>5a6ae77217978cb7b958a240c2e80b57</code>
+        <br>SHA-1: <code>ac1d8a8e4f4a1dca5864dc733caa940bffc28616</code></td>
+      </tr>
+      <tr id="razor">
+        <td>Nexus 7 (Wifi) <br>"razor"</td>
+        <td><a href="#top" onclick="onDownload(this)"
+          >razor-lpv79-preview-d0ddf8ce.tgz</a></td>
+        <td>MD5: <code>b293a5d3a4e07beabebcc0be85ad68a2</code>
+        <br><nobr>SHA-1: <code>d0ddf8ce733ba2a34279cdff8827fd604762c2342d</nobr></td>
+      </tr>
+    </table>
+  </li>
+
+  <li>Follow the instructions at
+  <a href="https://developers.google.com/android/nexus/images#instructions">developers.google.com/android</a>
+  to flash the image onto your device.</li>
+</ol>
+
+<h3 id="revertDevice">Revert a Device to Factory Specifications</h3>
+
+  <p>If you want to uninstall the L Preview and revert the device to factory
+specifications, go to <a href="http://developers.google.com/android
+/nexus/images">developers.google.com/android</a> and download the image you want
+to flash to for your device. Follow the instructions on that page to flash the
+image to your device.</p>
+
+<h3 id="setupAVD">Set up an AVD</h3>
+
+<p>You can set up <a href="{@docRoot}tools/devices/">Android Virtual Devices
+(AVD)</a> and use the emulator to build and test apps with the L Preview.</p>
+
+<p>To create an AVD with the AVD Manager:</p>
+
+<ol>
+  <li>Install the L Preview SDK in your development environment, as described
+      in <a href="{@docRoot}preview/setup-sdk.html">Setting Up the Preview
+      SDK.</a></li>
+  <li>Follow the steps in
+      <a href="{@docRoot}tools/devices/managing-avds.html">Managing AVDs with AVD
+      Manager</a>. Use the following settings:
+    <ul>
+      <li><b>Device:</b> Either Nexus 5 or Nexus 7</li>
+      <li><b>Target:</b> <!-- Confirm exact text when we have final distro -->
+       Android L (Preview) - API Level L</li>
+    </ul>
+    <!-- Confirm this works when you can download image through SDK manager! -->
+  </li>
+</ol>
+
+<h2 id="createProject">Create a Project</h2>
+
+<p>Android Studio makes it easy to create a project for the L Developer Preview. Follow
+the steps described in <a href="{@docRoot}sdk/installing/create-project.html">Creating a
+Project</a>. In the <strong>Form Factors</strong> screen:</p>
+
+<ul>
+  <li>Check <strong>Phone and Tablet</strong>.</li>
+  <li>Select <strong>API 20+: Android L (Preview)</strong> in <strong>Minimum SDK</strong>.</li>
+</ul>
+
+<p>On the development environment, open the <code>build.gradle</code> file for your module
+and make sure that:</p>
+
+<ul>
+  <li><code>compileSdkVersion</code> is set to <code>'android-L'</code></li>
+  <li><code>minSdkVersion</code> is set to <code>'L'</code></li>
+  <li><code>targetSdkVersion</code> is set to <code>'L'</code></li>
+</ul>
+
+<p>To use the material theme, open the <code>values/styles.xml</code> in your project and make
+sure that you theme extends the material theme:</p>
+
+<pre>
+&lt;resources>
+    &lt;style name="AppTheme" parent="android:Theme.Material">
+        &lt;!-- Customize your theme here -->
+    &lt;/style>
+&lt;/resources>
+</pre>
+
+  </div><!-- landing -->
+
+</div><!-- relative wrapper -->
+
+
+
+<script>
+  var urlRoot = "http://storage.googleapis.com/androiddevelopers/preview/";
+  function onDownload(link) {
+
+    $("#downloadForRealz").html("Download " + $(link).text());
+    $("#downloadForRealz").attr('href', urlRoot + $(link).text());
+
+    $("#tos").fadeIn('fast');
+    $("#landing").fadeOut('fast');
+
+    return true;
+  }
+
+
+  function onAgreeChecked() {
+    /* verify that the TOS is agreed */
+    if ($("input#agree").is(":checked")) {
+      /* reveal the download button */
+      $("a#downloadForRealz").removeClass('disabled');
+    } else {
+      $("a#downloadForRealz").addClass('disabled');
+    }
+  }
+
+  function onDownloadForRealz(link) {
+    if ($("input#agree").is(':checked')) {
+      $("#tos").fadeOut('fast');
+      $("#landing").fadeIn('fast');
+      _gaq.push(['_trackEvent', 'L Preview', 'System Image', $("#downloadForRealz").html()]);
+      location.hash = "";
+      return true;
+    } else {
+      $("label#agreeLabel").parent().stop().animate({color: "#258AAF"}, 200,
+        function() {$("label#agreeLabel").parent().stop().animate({color: "#222"}, 200)}
+      );
+      return false;
+    }
+  }
+
+  $(window).hashchange( function(){
+    if (location.hash == "") {
+      location.reload();
+    }
+  });
+
+</script>
\ No newline at end of file
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
new file mode 100644
index 0000000..9d7844b
--- /dev/null
+++ b/docs/html/preview/support.jd
@@ -0,0 +1,108 @@
+page.title=Support
+
+@jd:body
+
+<p>If you've encountered bugs or have feedback about the L Developer Preview,
+<a href="https://code.google.com/p/android-developer-preview/">create an issue</a> on
+our issue tracker.</p>
+
+<p>For more support,
+<a href="https://plus.google.com/communities/101985907812750684586">join
+the L Developer Preview Google+ community</a> to discuss your development experiences.
+
+
+<h2 id="ReleaseNotes">Release Notes</h2>
+<p>June 25, 2014 - Initial Release of the L Developer Preview</p>
+
+<h3 id="UserInterface">User interface</h3>
+<ul>
+<li>If your app launches an activity with
+{@link android.app.Activity#startActivity startActivity()}
+and an {@link android.content.Intent} set to
+{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, the
+activity shows up as a separate task in the Recent apps screen. This is the
+same behavior as though your app used {@code Intent.FLAG_ACTIVITY_NEW_DOCUMENT}
+(see <a href="{@docRoot}preview/api-overview.html#Recents">Concurrent
+documents and activities in the Recents screen</a>). If you want your activity
+to remain in the same task that launched it, use
+{@link android.app.Activity#startActivityForResult
+startActivityForResult()} instead.</li>
+<li>System-rendered shadows for user interface (UI) elements in views may
+appear with visible spiky edges. To avoid this visual artifact, use a higher
+<a href="{@docRoot}preview/material/views-shadows.html#elevation">view
+elevation</a>.</li>
+<li>On very tall or wide views, view shadows may appear with additional rough
+visual artifacts around the view edges. To minimize this, avoid using view
+shadows with very narrow views.</li>
+<li>The {@code android.graphics.drawable.RippleDrawable} class does not
+respond to pointer location changes, except when the drawable is set as a
+{@link android.view.View} background.</li>
+</ul>
+
+<h3 id="Multimedia">Multimedia</h3>
+<ul>
+<li>The {@code android.hardware.camera2} APIs are supported only on Nexus 5
+devices.</li>
+<li>Saving a DNG file with the new {@code android.hardware.camera2.DngCreator}
+API fails if lens shading compensation map generation is not enabled. To
+capture images to DNG files, add the following code when creating your capture
+requests:
+<pre>
+CaptureRequest.Builder stillCaptureRequest =
+        mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+
+stillCaptureRequest.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+        CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+</pre></li>
+<li>The {@code android.media.AudioTrack.write(float[], int, int, int)} method
+currently does not work. Use the
+{@link android.media.AudioTrack#write(short[], int, int)
+AudioTrack.write(short[], int, int)} method instead.</li>
+<li>Lockscreen security is currently not enforced when users start a Android
+mirroring session from the Quick Settings shade.</li>
+</ul>
+
+<h3 id="UserInput">User input</h3>
+<ul><li>The System UI may crash unexpectedly while the device is charging, if
+	the locale is set to {@code fr} (FRENCH).</li></ul>
+
+<h3 id="Wireless">Wireless and Connectivity</h3>
+<ul>
+<li>The {@code android.bluetooth.le} APIs are supported only on Nexus 5
+devices.</li>
+<li>You might encounter these issues while using Bluetooth LE scanning:
+	<ul>
+	<li><em>Settings</em> does not show all Bluetooth LE devices when a scan
+		filter is set.</li>
+	<li>System returns non-intuitive error messages during a Bluetooth LE scan,
+		when Bluetooth is off.</li>
+	<li>The {@code BluetoothLeScanner.startScan()} method starts failing after
+		six concurrent scans with different callbacks.</li>
+	</ul>
+</li>
+<li>You might encounter these issues while using Bluetooth LE advertising:
+	<ul>
+		<li>The device MAC address does not change for multiple advertising
+		when the application processor is asleep.</li>
+		<li>The TX Power Level is always 0 in advertising packets.</li>
+	</ul>
+</li>
+</ul>
+
+<h3 id="Enterprise">Enterprise</h3>
+<ul>
+<li>The device may crash unexpectedly in these situations when using
+Android work functionality:
+<ul>
+<li>The user attempts to share a web page (via <strong>Menu &gt; Share</strong>)
+from a non-Android work Chrome app to a Android work profile Gmail app.</li>
+<li>The user attempts to share a web page via Bluetooth from a
+Android work profile
+Chrome app.</li>
+<li>The user attempts to share a web page via Android Beam from a
+Android work profile Chrome app.</li>
+</ul>
+</li>
+<li>Deleting a Android work profile may take several minutes to complete. You
+cannot create a new Android work profile until the deletion operation is over.</li>
+</ul>
diff --git a/docs/html/preview/tv/adt-1/index.jd b/docs/html/preview/tv/adt-1/index.jd
new file mode 100644
index 0000000..d83dd11
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/index.jd
@@ -0,0 +1,282 @@
+page.title=ADT-1 Developer Kit
+page.tags="emote","e-mote","adt"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#faq">Frequently Asked Questions</a>
+      <ol>
+        <li><a href="#setup">Device Setup</a></li>
+        <li><a href="#input">User Input</a></li>
+        <li><a href="#cast">Google Cast</a></li>
+        <li><a href="#trouble">Troubleshooting</a></li>
+      </ol>
+    </li>
+    <li><a href="#emote">Android TV Remote Control App</a></li>
+    <li><a href="#reg-safety">Regulatory Disclosures and Safety</a></li>
+  </ol>
+</div>
+</div>
+
+<p>The ADT-1 Developer Kit is a streaming media player and game controller designed for running
+and testing apps built for Android TV. Supplies of ADT-1 are limited and it is intended for
+developers who are interested in building new apps or extending their existing apps to run on the
+Android TV platform.</p>
+
+<p class="note">
+  <strong>Note:</strong> The ADT-1 kit <em>is not required</em> for building and testing apps
+  for Android TV. You can build apps for TV and test them using an emulator for TV devices. The
+  L Developer Preview includes all the software needed to build TV apps and an emulator for running
+  and testing them. For more information, see the
+  <a href="{@docRoot}preview/tv/start/index.html">Get Started</a> guide for TV apps.
+</p>
+
+<h2 id="faq">ADT-1 Frequently Asked Questions</h2>
+
+<p>The following information is provided to help set up and use the ADT-1 device.</p>
+
+
+<h3 id="setup">Device Setup</h3>
+
+<p>
+  <strong>How do I turn my device on?</strong>
+</p>
+<p>Plug the included power cable into the back of ADT-1. The device does not have an on/off
+  switch.</p>
+
+<p>
+  <strong>How do I completely turn my device off? </strong>
+</p>
+<p>Unplug the included power cable from the back of ADT-1. The device does not have an on/off
+  switch. However, ADT-1 will begin sleeping (daydream) based on user settings in
+  <strong>Settings &gt; Display &gt; Daydream</strong>.
+  </p>
+
+<p>
+  <strong>How do I connect to the network?</strong>
+</p>
+<p>ADT-1 has both wireless and Ethernet for connecting to your network. To change your wireless
+  network, go to <strong>Settings -&gt; Wi-Fi</strong>. To use an Ethernet network connection,
+  simply plug an Ethernet cable (that is connected to your network) into the port on the back of
+  ADT-1.</p>
+
+<p>
+  <strong>How do I use the developer cable?</strong>
+</p>
+<p>The developer cable has three connectors: a small, male power connector that plugs into the
+  power port on the back of ADT-1, a standard male USB-A connector that connects your PC, and a
+  small, female power connector that the included power supply plugs into.</p>
+
+
+
+<h3 id="input">User Input</h3>
+
+<p>
+  <strong>How do I put the gamepad that came with my ADT-1 into pairing mode?</strong>
+</p>
+<p>Press and hold the Back and Home buttons together for about three seconds, until all four
+  blue LEDs flash together. When the LEDs are flashing, the gamepad is in pairing mode.</p>
+
+<p>
+  <strong>How do I use the gamepad with the on-screen keyboard?</strong>
+</p>
+<p>Use the D-pad or left joystick to move the cursor, and press A to select. Press X to delete a
+  character, and press Y to insert a space. Also, you can press the right joystick to toggle caps
+  lock, and press the left joystick to show additional symbols.</p>
+
+<p>
+  <strong>Can I control ADT-1 with my phone or tablet?</strong>
+</p>
+<p>Yes. In order to control the ADT-1 with Android phones or tablets, you can download a remote
+  control app from the Google Play store. For more information, see <a href="#emote">Android TV
+  Remote Control App</a>.
+  </p>
+
+<p>
+  <strong>Can I connect a USB keyboard or mouse to ADT-1?</strong>
+</p>
+<p>Yes, you can connect a USB keyboard or mouse to the USB port on the back of ADT-1.
+
+<p class="note">
+  <strong>Note:</strong> The ADT-1 device is not compatible with all manufacturers and models of
+  these devices. If a particular keyboard or mouse does not work, try a different model.
+</p>
+
+<p>
+  <strong>How do I connect a Bluetooth device without an input device already attached?</strong>
+</p>
+<p>You can put ADT-1 into Bluetooth pairing mode using a hardware button. Press the small, round
+  button on the back of ADT-1 to make it search for Bluetooth devices in pairing mode. If multiple
+  accessories are found, press the small, round button to select the device you want to pair.
+  Pairing will happen automatically after a few seconds.
+</p>
+
+<p>
+  <strong>How do I connect additional Bluetooth accessories?</strong>
+<p>
+<p>To pair Bluetooth devices to ADT-1 from the user interface, go to <strong>Settings &gt;
+  Remote &amp; Accessories &gt; Add accessory</strong>
+
+
+<h3 id="cast">Google Cast</h3>
+
+<p>
+  <strong>Can I cast to an ADT-1 device?</strong>
+<p>
+<p>Yes. The ADT-1 includes Google Cast receiver functionality, similar to Chromecast. Since the
+  ADT-1 is a developer device running a development software release, the Google Cast receiver is
+  open only to a limited number of apps.</p>
+
+<p>
+  <strong>Which Cast apps are supported on ADT-1?</strong>
+<p>
+<p>As a developer device, the ADT-1 supports casting from only the following apps/websites:</p>
+
+<ul>
+  <li>YouTube</li>
+  <li>Netflix</li>
+  <li>Google+ Photos</li>
+  <li>Google Play Movies and TV (Android only)</li>
+</ul>
+
+<p>Coming soon:</p>
+
+<ul>
+  <li>Google Play Music</li>
+  <li>Google Play Movies and TV (iOS and Chrome)</li>
+  <li>Mirror you Android device screen to ADT-1</li>
+</ul>
+
+<p class="note">
+  <strong>Note:</strong> When casting from a Chrome browser, you must use Chrome V.36 or higher.
+  Chrome V.36 is available in beta-channel and is planned to be released soon.
+</p>
+
+<p>
+  <strong>How do I cast to ADT-1?</strong>
+<p>
+<p>You cast to an ADT-1 device the same way you do with a Chromecast device. Open the supported
+  Cast apps or webpages, press the <strong>Cast</strong> button and you should see the ADT-1 as a
+  Cast target. For more infomation about on how to cast, see
+  <a href="http://www.google.com/intl/en/chrome/devices/chromecast/learn.html">Learn How to
+  Cast</a>.
+  </p>
+
+<p>
+  <strong>Will my Google Cast sender apps work on ADT-1 just like Chromecast?</strong>
+<p>
+<p>Yes. Your Cast app works on ADT-1 and Android TV products without additional work.<p>
+
+<p class="note">
+  <strong>Note:</strong> Your iOS sender app requires the Google Cast iOS API version 2.2.1
+  or later to work with the ADT-1 device.
+</p>
+
+<p>
+  <strong>How do I register my ADT-1 in order to run my apps?</strong>
+</p>
+<ol>
+  <li>Go to <strong>Settings &gt; Google Cast</strong> and turn on developer support, allowing the
+    ADT-1 device to send its serial number to Google.</li>
+  <li>Register your ADT-1 device in the Google Cast Developer Console, using the 12 character
+    serial number engraved on the back of the ADT-1.</li>
+</ol>
+
+<p>For more Google Cast developer information, see the
+  <a href="https://developers.google.com/cast/">Cast developer site</a>. Please use the Google Cast
+  SDK <a href="https://code.google.com/p/google-cast-sdk/issues/list">issue tracker</a> for filing
+  issues related to Cast. Make sure you mention the ADT-1 device when filing an issue.
+</p>
+
+<p>
+  <strong>How do I debug my Cast app on ADT-1?</strong>
+</p>
+<p>Connect your development platform using the power/USB cable, and using a Chrome browser,
+  navigate to <code>chrome://inspect/#devices</code> to debug the webview.</p>
+
+
+<h3 id="trouble">Troubleshooting</h3>
+
+<p>
+  <strong>Why doesn't the on-screen keyboard come up?</strong>
+</p>
+<p>Enable the keyboard in the device Settings. Go to <strong>Settings &gt; Keyboard &gt; Current
+  keyboard</strong> and choose <strong>Leanback keyboard</strong>.
+
+<p>
+  <strong>How do I perform a hardware reboot?</strong>
+</p>
+<p>Locked it up, huh? No worries. We've done that a few times ourselves. Unplug and replug the
+  included power cable from the back of ADT-1 to reboot it.
+</p>
+
+<p>
+  <strong>How do I perform a factory reset?</strong>
+</p>
+<p class="warning">
+  <strong>Warning:</strong> This procedure removes all data from the device, including system
+  data, downloaded apps, app data, and account settings.
+</p>
+
+<p>From the home screen, go to <strong>Settings &gt; Device &gt; Factory data reset</strong>, and
+  select <strong>Reset device</strong>.
+</p>
+
+<p>
+  <strong>How do I perform a hardware reset?</strong>
+</p>
+<p class="warning">
+  <strong>Warning:</strong> This procedure performs a factory data reset, removing all data from
+  the device, including system data, downloaded apps, app data, and account settings.
+</p>
+
+<p>Unplug the power cable from the back of ADT-1. Press and hold the small, round button on the
+  back of ADT-1 as you re-insert the power cable, and continue to hold the small round button. The
+  LED will begin flashing red for a few seconds, then change to multi-color cycle. When the LED
+  starts the multi-color cycle, release the small, round button, and ADT-1 boots up. If you release
+  the button while the LED is flashing red, the device will be in Fastboot mode.</p>
+
+<p>
+  <strong>There is a hardware problem with my ADT-1. How do I return it?</strong>
+</p>
+<p>You can request a return of the device using the
+  <a href="https://support.google.com/googleplay/android-developer/contact/adt_rma">return
+  merchandise authorization form</a>.
+</p>
+
+
+<h2 id="emote">Android TV Remote Control App</h2>
+
+<div class="figure" style="width:250px;margin-top:0">
+<img src="/preview/tv/images/android-tv-remote.png" alt="Android TV Remote Screenshots">
+</div>
+
+<p>A remote control app is available for Android phones and tablets that allows you to interact
+  with the ADT-1 device. This app allows you to switch between D-pad input mode or touchpad mode
+  to navigate content and play games on a Android TV device. You can also tap the mic button to
+  start a voice search, or use the keyboard to input text using this app.</p>
+
+<p>You download the remote control app from the Google Play store using
+  <a href="https://play.google.com/store/apps/details?id=com.google.android.tv.remote">this
+  link</a>.
+</p>
+
+<p class="note">
+  <strong>Note:</strong> your Android ​phone or tablet must be connected to the same local network
+  as ADT-1.
+</p>
+
+
+<h2 id="reg-safety">Regulatory Disclosures and Safety Information</h2>
+
+<p>The ADT-1 device comes with important regulatory disclosures and safety information. Please
+read this information before using the device:</p>
+
+<ul>
+  <li><a href="regulatory.html">Regulatory Disclosures</a></li>
+  <li><a href="safety.html">Important Safety Information</a></li>
+</ul>
+
diff --git a/docs/html/preview/tv/adt-1/regulatory.jd b/docs/html/preview/tv/adt-1/regulatory.jd
new file mode 100644
index 0000000..2f5bf7e
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/regulatory.jd
@@ -0,0 +1,79 @@
+page.title=Regulatory Disclosures for ADT-1
+parent.title=ADT-1 Developer Kit
+parent.link=index.html
+
+@jd:body
+
+<p>Disclosures for the <a href="index.html">ADT-1</a> device.</p>
+
+<p>
+  Model: W2<br>
+  FCC ID: A4R-W2<br>
+  IC: 10395A-W2
+</p>
+
+<p>U.S. Federal Communications Commission Notices</p>
+<p>To satisfy FCC and IC exposure requirements, a separation distance of at least 20 cm should
+  be maintained between the antenna of this device and persons during device operation. Operations
+  at closer than this distance are not recommended.</p>
+<p>The antenna used for this transmitter must not be co-located in conjunction with any other
+  antenna or transmitter.</p>
+<p>This equipment has been tested and found to comply with the limits for a Class B digital
+  device, pursuant to part 15 of the FCC Rules. These limits are designed to provide reasonable
+  protection against harmful interference in a residential installation. This equipment generates,
+  uses and can radiate radio frequency energy and, if not installed and used in accordance with the
+  instructions, may cause harmful interference to radio communications. However, there is no
+  guarantee that interference will not occur in a particular installation. If this equipment does
+  cause harmful interference to radio or television reception, which can be determined by turning
+  the equipment off and on, the user is encouraged to try to correct the interference by one or more
+  of the following measures:</p>
+<p>—Reorient or relocate the receiving antenna.</p>
+<p>—Increase the separation between the equipment and receiver.</p>
+<p>—Connect the equipment into an outlet on a circuit different from that to which the receiver
+  is connected.</p>
+<p>—Consult the dealer or an experienced radio/ TV technician for help.</p>
+<p>This device complies with part 15 of the FCC Rules. Operation is subject to the following two
+  conditions: (1) This device may not cause harmful interference, and (2) this device must accept
+  any interference received, including interference that may cause undesired operation.</p>
+<p>Changes or modifications not expressly approved by Google Inc. could void the user's
+  authority to operate the equipment.</p>
+<p>Industry Canada Notices</p>
+<p>This device complies with Industry Canada licence-exempt RSS standard(s). Operation is
+  subject to the following two conditions: (1) this device may not cause interference, and (2) this
+  device must accept any interference, including interference that may cause undesired operation of
+  the device.</p>
+<p>Under Industry Canada regulations, this radio transmitter may only operate using an antenna
+  of a type and maximum (or lesser) gain approved for the transmitter by Industry Canada. To reduce
+  potential radio interference to other users, the antenna type and its gain should be so chosen
+  that the equivalent isotropically radiated power (e.i.r.p.) is not more than that necessary for
+  successful communication.</p>
+<p>The radiated output power of the Wireless Device is below the Industry Canada (IC) radio
+  frequency exposure limits. The Wireless Device should be used in such a manner such that the
+  potential for human contact during normal operation is minimized.</p>
+
+<hr />
+
+<p>CAN ICES-3 (B)/NMB-3(B)</p>
+<p>
+  <u>Avis d’<em>Industrie Canada</em></u>
+</p>
+<p>
+  Le présent appareil est conforme aux <em>CNR</em> d'Industrie Canada applicables aux appareils
+  radio exempts de licence. L'exploitation est autorisée aux deux conditions suivantes : (1)
+  l'appareil ne doit pas produire de brouillage, et (2) l'appareil doit accepter tout brouillage
+  radioélectrique subi, même si le brouillage est susceptible d'en compromettre le fonctionnement.
+</p>
+<p>
+  En vertu de la règlementation d’<em>Industrie Canada</em>, cet émetteur radio peut
+    fonctionner avec une antenne d'un type et d'un gain maximal (ou inférieur) approuvé pour
+    l'émetteur par <em>Industrie Canada</em>. Dans le but de réduire les risques de brouillage
+  radioélectrique à l'intention des autres utilisateurs, il faut choisir le type d'antenne et son
+  gain de sorte que la puissance isotrope rayonnée équivalente (p.i.r.e.) ne dépasse pas l'intensité
+  nécessaire à l'établissement d'une communication satisfaisante.
+</p>
+<p>
+  La puissance rayonnée en sortie de l'appareil sans fil est inférieure aux limites fixées par
+  <em>Industrie Canada</em> en matière d'exposition aux radiofréquences. L'appareil sans fil
+  doit être utilisé de sorte que la possibilité d'un contact humain pendant le fonctionnement
+  normal soit limitée.
+</p>
diff --git a/docs/html/preview/tv/adt-1/safety.jd b/docs/html/preview/tv/adt-1/safety.jd
new file mode 100644
index 0000000..1984853
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/safety.jd
@@ -0,0 +1,140 @@
+page.title=Important Safety Instructions for ADT-1
+parent.title=ADT-1 Developer Kit
+parent.link=index.html
+
+@jd:body
+
+<p>Safety information for the <a href="index.html">ADT-1</a> device.</p>
+
+<p>
+  <strong>WARNING:</strong> Read all safety information below before using this device to avoid
+  injury.
+</p>
+<ul>
+  <li><p>Do not install near heat sources, such as heaters and other devices.</p></li>
+  <li><p>Use in a well-ventilated area and plug power adapter into an easily accessible
+      outlet. Only use this device with the provided power adapter.</p></li>
+  <li><p>The device has no on/off switch. To disconnect from power, you must unplug the
+      power adapter.</p></li>
+  <li><p>Only use indoors and do not expose to rain, liquid, moisture, excessive heat, or
+      naked flame.</p></li>
+  <li><p>Clean only with a dry cloth.</p></li>
+</ul>
+<p>
+  <strong>WARNING:</strong> Playing video games has been linked to injuries in some
+  users. Read all safety and health information below before using the gamepad to avoid possible
+  injury.
+</p>
+
+<p><u>Photosensitive Seizures</u></p>
+
+<p>
+  A very small percentage of people may experience a seizure when exposed to certain visual images,
+  including flashing lights or patterns that may appear in some video games, even people who have no
+  history of seizures or epilepsy. These seizures have a variety of symptoms, including
+  lightheadedness, altered vision, disorientation, loss of awareness, involuntary movements, loss of
+  consciousness, or convulsions. If you experience any of these symptoms, <u>stop gaming
+    immediately and consult your doctor</u>.
+</p>
+
+<p><u>Ergonomics</u></p>
+
+<p>Long periods of repetitive motion using incorrect body positioning may be associated with
+  physical discomfort and injuries to nerves, tendons, and muscles. If during or after gaming you
+  feel pain, numbness, weakness, swelling, burning, cramping, or stiffness, <u>stop gaming
+  and consult your doctor</u>.
+
+<p>
+  <strong>Healthy Gaming</strong>
+</p>
+
+<p>To reduce risk of seizures or injury, take the following precautions:</p>
+
+<ul>
+  <li><p>Sit as far away from the TV screen as possible.</p></li>
+  <li><p>Play in a well-lit room.</p></li>
+  <li><p>Do not play when you are drowsy or fatigued.</p></li>
+  <li><p>Take 10-15 minute breaks every hour if playing video games and avoid prolonged
+      gaming.</p></li>
+</ul>
+
+<p>
+  <strong>Do Not Attempt Repairs Yourself</strong>
+</p>
+
+<p>There are no user-serviceable parts inside. Do not attempt to open or disassemble.</p>
+
+<p>Failure to follow these safety instructions could result in fire, electric shock, damage to
+  the device or other property, or personal injury.</p>
+
+<hr />
+
+<p>
+  <strong>Importantes instructions concernant la sécurité</strong>
+</p>
+
+<p>
+  <strong>ATTENTION:</strong> Veuillez lire toutes les informations de sécurité énoncées ci-bas
+  avant d’utiliser l’appareil pour éviter des blessures.
+</p>
+
+<ul>
+  <li><p>Ne pas installer à proximité d’une source de chaleur telle une chaufferette ou un
+      autre appareil similaire.</p></li>
+  <li><p>Utiliser dans un endroit bien aéré et brancher l’adaptateur électrique dans une
+      prise de courant facilement accessible.</p></li>
+  <li><p>L’appareil ne possède aucun interrupteur marché/arrêt. Pour mettre l’appareil hors
+      tension, il faut débrancher l’appareil de la prise de courant.</p></li>
+  <li><p>Utiliser l’appareil uniquement à l’intérieur et ne pas l’exposer à la pluie, à des
+      substances liquides, à l’humidité, à la chaleur excessive ou à une flamme.</p></li>
+  <li><p>Nettoyer uniquement avec un linge sec.</p></li>
+</ul>
+
+<p>
+  <strong>ATTENTION:</strong> Le fait de jouer à des jeux vidéo a été relié à des blessures chez certains
+  utilisateurs. Afin d’éviter de possibles blessures, veuillez lire toutes les informations
+  concernant la sécurité et la santé énoncées ci-bas avant d’utiliser la tablette de jeu.
+</p>
+
+<p><u>Épilepsie photosensible</u></p>
+
+<p>L’exposition à certaines images visuelles, incluant les lumières ou motifs clignotants qui
+  peuvent apparaître dans certains jeux vidéo, peut provoquer chez un très faible pourcentage de
+  personnes une crise d’épilepsie, et ce, même si ces personnes n’ont aucun historique de crises ou
+  d’épilepsie. Ces crises comportent divers symptômes tels que des étourdissements, une vision
+  altérée, un sentiment de désorientation, la perte de conscience, des mouvements involontaires, la
+  perte de connaissance ou de conscience ou des convulsions. Si vous ressentez quelconque de ces
+  symptômes, <u>cessez de jouer immédiatement et consultez votre médecin</u>.</p>
+
+<p><u>Ergonomie</u></p>
+
+<p>Les longues périodes de mouvements répétitifs effectués dans une position corporelle
+  inadéquate peuvent mener à un inconfort physique et à des blessures aux nerfs, tendons et muscles.
+  Si durant ou après avoir joué à des jeux vidéo, vous ressentez de la douleur, de
+  l’engourdissement, une faiblesse, de l’inflammation, une sensation de brûlure, des crampes ou de
+  la rigidité, <u>cessez de jouer immédiatement et consultez votre médecin</u>.</p>
+
+<p>
+  <strong>Le jeu sécuritaire</strong>
+</p>
+
+<p>Afin de réduire les risques de crises d’épilepsie ou de blessures, veuillez prendre les
+  précautions suivantes :</p>
+
+<ul>
+  <li>Asseyez-vous aussi loin de l’écran de télévision que possible.</li>
+  <li>Jouez dans une pièce munie d’un éclairage adéquat.</li>
+  <li>Ne jouez pas lorsque vous êtes étourdi ou fatigué.</li>
+  <li>Prenez 10 à 15 minutes de pause après chaque heure de jeu et évitez les périodes de jeu
+  prolongées.</li>
+</ul>
+
+<p>
+  <strong>Ne pas tenter d’effectuer des réparations par vous-même</strong>
+</p>
+
+<p>L’Appareil ne contient aucune pièce pouvant être réparée par l’utilisateur. Ne pas tenter
+  d’ouvrir ou de désassembler l’Appareil.</p>
+
+<p>Le défaut de suivre ces instructions de sécurité pourrait provoquer un feu, un choc
+  électrique, un dommage à l’Appareil ou à d’autres objets ou des lésions corporelles.</p>
diff --git a/docs/html/preview/tv/design/images/apps-games-rows.jpg b/docs/html/preview/tv/design/images/apps-games-rows.jpg
new file mode 100644
index 0000000..5023655
--- /dev/null
+++ b/docs/html/preview/tv/design/images/apps-games-rows.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/atv-framed-med.png b/docs/html/preview/tv/design/images/atv-framed-med.png
new file mode 100644
index 0000000..e06f6e7
--- /dev/null
+++ b/docs/html/preview/tv/design/images/atv-framed-med.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/atv-home.jpg b/docs/html/preview/tv/design/images/atv-home.jpg
new file mode 100644
index 0000000..4b25bab
--- /dev/null
+++ b/docs/html/preview/tv/design/images/atv-home.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/focus.png b/docs/html/preview/tv/design/images/focus.png
new file mode 100644
index 0000000..df61f4d
--- /dev/null
+++ b/docs/html/preview/tv/design/images/focus.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/icon.png b/docs/html/preview/tv/design/images/icon.png
new file mode 100644
index 0000000..ae34e18
--- /dev/null
+++ b/docs/html/preview/tv/design/images/icon.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/overscan.png b/docs/html/preview/tv/design/images/overscan.png
new file mode 100644
index 0000000..fb7e4bc
--- /dev/null
+++ b/docs/html/preview/tv/design/images/overscan.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/recommendations.png b/docs/html/preview/tv/design/images/recommendations.png
new file mode 100644
index 0000000..942cd10
--- /dev/null
+++ b/docs/html/preview/tv/design/images/recommendations.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/search.jpg b/docs/html/preview/tv/design/images/search.jpg
new file mode 100644
index 0000000..c034939
--- /dev/null
+++ b/docs/html/preview/tv/design/images/search.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/settings.jpg b/docs/html/preview/tv/design/images/settings.jpg
new file mode 100644
index 0000000..1c5bf31
--- /dev/null
+++ b/docs/html/preview/tv/design/images/settings.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/index.jd b/docs/html/preview/tv/design/index.jd
new file mode 100644
index 0000000..b924a5c
--- /dev/null
+++ b/docs/html/preview/tv/design/index.jd
@@ -0,0 +1,65 @@
+page.title=Design for TV
+header.justLinks=1
+footer.hide=1
+@jd:body
+
+
+<p>The Android TV platform user interface provides the launch pad for your app's big screen
+  experience. It's important to understand how your app is presented in the main user interface and
+  how your app can help users get to the content they want quickly.</p>
+
+
+<h2>Home Screen</h2>
+
+<p>The Home Screen is the start of the user experience, providing search, content
+  recommendations, and access to apps and settings. This screen provides a rich and cinematic
+  overview of apps and content.</p>
+
+<img src="{@docRoot}preview/tv/design/images/atv-home.jpg" alt="TV Home screen" />
+
+
+<h2>Search</h2>
+
+<p>By bringing the power of Google search to the big screen, Android TV makes new, dynamic
+  connections between content. A favorite movie may lead to the discovery of a new music artist,
+  planning a trip to Paris might surface new YouTube content and photos.</p>
+
+<img src="{@docRoot}preview/tv/design/images/search.jpg" alt="Recommendations Row" />
+
+<p>To learn more about searching within your app, see
+  <a href="{@docRoot}preview/tv/ui/in-app-search.html">Searching in TV Apps</a>.
+
+<h2>Recommendations</h2>
+
+<p>The recommendations row on Android TV is a central feature of the Home Screen that allows
+  users quick access to dynamic and relevant content for their media-consumption activities. The
+  row is optimized for quick browsing of personalized content and activity resumption (on the
+  device and across devices), while also providing a way for users to act on meaningful new
+  content.</p>
+
+<img src="{@docRoot}preview/tv/design/images/recommendations.png" alt="Recommendations Row" />
+
+<p>
+  Recommendations are based on the user’s recent and frequent usage behaviors, as well as
+  expressed content preferences. They appear as cards that represent a system or app action,
+  notification, activity, or piece of actionable media. Your app can provide suggestions for the
+  recommendations row to help get your content noticed. To learn more, see
+  <a href="{@docRoot}preview/tv/ui/recommendations.html">Recommendations</a>.
+</p>
+
+
+<h2>Apps and Games</h2>
+
+<p>Apps and Games rows both have special areas on the Home Screen. Within their respective
+  areas, Apps and Games titles are ordered to reflect the user’s recent usage.</p>
+
+<img src="{@docRoot}preview/tv/design/images/apps-games-rows.jpg" alt="Apps and Games Rows" />
+
+
+<h2>Settings</h2>
+
+<p>Access to Settings is found at the bottom of the Home Screen. From here, the user can access
+  Android and device-specific settings.
+</p>
+
+<img src="{@docRoot}preview/tv/design/images/settings.jpg" alt="Settings Row" />
diff --git a/docs/html/preview/tv/design/patterns.jd b/docs/html/preview/tv/design/patterns.jd
new file mode 100644
index 0000000..cdba74c
--- /dev/null
+++ b/docs/html/preview/tv/design/patterns.jd
@@ -0,0 +1,86 @@
+page.title=Patterns for TV
+page.tags="design"
+@jd:body
+
+<p>As a developer of apps for TV, you should follow certain patterns to enable users to
+  quickly understand and operate your app. This section describes recommended design patterns
+  for TV apps.</p>
+
+<h2>Navigation, Focus and Selection</h2>
+
+<p>Users typically navigate TV devices using a directional pad (D-Pad). This type of controller
+  limits movement to up, down, left, and right. As you design your app for TV, make sure your
+  user interface has clear paths for two-axis navigation by aligning objects in lists and
+  grids.</p>
+
+<img src="{@docRoot}preview/tv/design/images/focus.png" alt="TV navigation and focus diagram" />
+
+<p>A key aspect of making your application work well with a D-Pad controller is to make sure
+  that there is always an object that is obviously in focus. Your app must clearly indicate
+  what object is focused, so users can easily see what action they can take. Use scale, shadow
+  brightness, opacity, animation or a combination of these attributes to help users see a focused
+  object.</p>
+
+
+<h2>Icons</h2>
+
+<p>Apps on TV devices require some additional icon images for presentation in the system
+  user interface, including home screen launcher images (banners) and recommendation icons.
+  The visual specifications for these icons are shown below.</p>
+
+
+<h3>Banners</h3>
+
+<p>App Banners represent your app on the home screen of TV devices and serve and as a way for
+  users to launch your app. Here are specific requirements for a banner image:
+</p>
+
+<ul>
+  <li>Size: 320 x 180 px, xhdpi resource</li>
+  <li>Text should be included in the image. If your app is available in more than one
+      language, you must provide versions of the banner image for each supported language.</li>
+</ul>
+
+
+<h3>Recommendation Icons</h3>
+
+<p>Recommendation cards include a small icon that is imposed over a colored background.
+  An example and specifications for the this icon are shown below:</p>
+
+<img src="{@docRoot}preview/tv/design/images/icon.png" alt="Recommendation icon examples" />
+
+<p>Here are the requirements for recommendation icons:</p>
+
+<ul>
+  <li>Monocolor: size 16x16dp, white (#fff) icon with transparent background, PNG format</li>
+  <li>Graphics should be centered within the icon image</li>
+</ul>
+
+<p class="note">
+  <strong>Note:</strong> Your app icon image may be desaturated and blended for some card
+  displays.
+</p>
+
+
+<h2>Background Images</h2>
+
+<p>Background images are displayed in the background of your app to provide additional visual
+  interest, information, or branding. The BrowseFragment and DetailsFragment classes in the Leanback
+  support library provide specific support for background images and for updating them as items gain
+  and lose focus. Here are the specific requirements for background images:</p>
+
+<ul>
+  <li>Full color, 1920 x 1080 pixels</li>
+</ul>
+
+<p class="note">
+  <strong>Note:</strong> If the image does not meet this requirement, it is scaled to fit.
+</p>
+
+<h2>Audio Feedback</h2>
+
+<p>Sounds on Android TV bring a cinematic quality to the interaction experience. You should
+  consider adding sounds for user actions or to provide feedback when a user is only partially
+  visually engaged with the screen (e.g., because they are distracted or multitasking).
+  You should also consider using sounds as alternatives to visual messages, for example to indicate
+  that a user has reached the end of a list or is trying to navigate to an undefined location.</p>
diff --git a/docs/html/preview/tv/design/principles.jd b/docs/html/preview/tv/design/principles.jd
new file mode 100644
index 0000000..106fa96
--- /dev/null
+++ b/docs/html/preview/tv/design/principles.jd
@@ -0,0 +1,33 @@
+page.title=Creative Vision for TV
+@jd:body
+
+<p>Users bring a specific set of expectations when watching TV, versus
+  interacting with a phone or tablet. These guidelines have been developed by the Android User
+  Experience Team to guide creation of the Android TV platform and the apps that run on it.</p>
+
+<h2>Casual Consumption</h2>
+
+<p>The TV is an entertainment interface, not a computer or mobile device. Optimize for
+  activities that put content at the center: from the casual posture of movie-watching, to
+  immersive gameplay, to hanging out with friends in a living room.</p>
+
+<p>Users expect immediate access to content when they turn on a TV. Get users into the action
+  fast, be it the big game, their favorite show, or a game with friends. The next piece of content
+  to watch or play should only be a click or two away.</p>
+
+
+<h2>Cinematic Experience</h2>
+
+<p>Create immersive experiences for the user. Design for as little user interface and as much
+  content as possible on each screen. Use visual imagery, movement, and sound to inform and delight
+  users. Avoid using on-screen text to convey information and purpose. Tell your story with pictures
+  and sound.</p>
+
+
+<h2>Simplicity</h2>
+
+<p>Android TV is simple and magical. It’s all about finding and enjoying content and
+  apps with the least amount of friction. Minimize the number of navigation steps required to
+  perform actions. Build apps with the fewest screens possible between app entry and content
+  immersion. Avoid making users enter text whenever possible, and use voice interfaces when you
+  require text input.</p>
diff --git a/docs/html/preview/tv/design/style.jd b/docs/html/preview/tv/design/style.jd
new file mode 100644
index 0000000..67a7096
--- /dev/null
+++ b/docs/html/preview/tv/design/style.jd
@@ -0,0 +1,101 @@
+page.title=Style for TV
+page.tags="design"
+@jd:body
+
+
+<p>Follow these style guidelines to create beautiful, functional apps for TV.</p>
+
+
+<h2>Layouts</h2>
+
+<p>The difference between a TV experience that feels right and one that does not greatly depends
+  on the number, spacing, and size of on-screen elements. Although TV sizes and resolutions have
+  steadily increased over time, users expect TV experiences to be relatively simple and
+  uncluttered.</p>
+
+<p>The additional resolution and screen area afforded by modern displays is best used to display
+  things at better quality, rather than greater quantity. For example, use your layouts to show
+  large, beautiful pieces of content, or to resize type for both easy reading and generous spacing.
+</p>
+
+<p>If you are creating an app for browsing and playing content, use the prebuilt fragments in the
+  Leanback support library. These layouts have been built specifically for use on TV devices with
+  the guidance of the Android User Experience team. For more information on using these classes,
+  see the <a href="{@docRoot}preview/tv/build-ui/index.html">User Interfaces</a> guide.
+</p>
+
+<p>Here are some additional recommendations for creating functional and attractive layouts for TV
+  apps:</p>
+
+<ul>
+  <li>Design layouts for landscape orientation. TV screens always use this
+      orientation.</li>
+  <li>Design your artwork assets for best viewing at HD resolution (1920 x 1080 pixels).</li>
+  <li>Put on-screen navigational controls on the left or right side of the screen, and
+      save the vertical space for content.</li>
+  <li>Use Fragments to create UIs that are divided into sections, and use view groups
+      like GridView instead of ListView to make better use of the horizontal screen space.</li>
+  <li>Avoid a cluttered interface by adding sufficient margins between layout controls.</li>
+</ul>
+
+
+<h3>Overscan</h3>
+
+<p>During the evolution of TV technology, overscan originally described an area of TV content
+  outside of a safe zone that most TVs could reliably display. Even on some of today’s HDTV flat
+  screens, areas outside that zone may not be visible.</p>
+
+<img src="{@docRoot}preview/tv/design/images/overscan.png" alt="Overscan borders for TV" />
+
+<p>Build a 10% margin into your TV screen designs to account for overscan area the TV may not
+  display correctly. On a 1920 x 1080 pixel screen, this margin should be a minimum of 27px from the
+  top and bottom edges and a minimum of 48px from the right and left edges of the picture.</p>
+
+
+<h2>Color</h2>
+
+<p>Color rendering on televisions can be imprecise compared to computer monitors or mobile
+  devices. LCD and Plasma TVs often apply smoothing and sharpening filters, and color rendering may
+  not match what you see on a computer screen.</p>
+
+<p>Subtle hue or brightness differences between elements may disappear or be over-emphasized on
+  TV screens. Some color gradient combinations will show bands. You should avoid pure whites on
+  large areas of the screen. For highly saturated colors (especially reds, greens and blues) you
+  should review them when used to fill significant areas of the screen.  You
+  should also avoid using very dark or muddy colors, as TV settings may display these colors with
+  exaggerated contrast, causing them to be indistinguishable.</p>
+
+
+<h2>Typography</h2>
+
+<p>The text and controls in a TV application's UI should be easily visible and navigable from a
+  distance. The minimum recommended font size for TV is 12sp. The default text size setting should
+  be 18sp. We recommend the following guidelines for TV apps:</p>
+
+<ul>
+  <li><strong>Card Titles:</strong> Roboto Condensed 16sp</li>
+  <li><strong>Card Subtext:</strong> Roboto Condensed 12sp</li>
+  <li><strong>Browse Screen Title:</strong> Roboto Regular 44sp</li>
+  <li><strong>Browse Category Title:</strong> Roboto Condensed 20sp</li>
+  <li><strong>Details Content Titles:</strong> Roboto Regular 34sp</li>
+  <li><strong>Details Subtext:</strong> Roboto Regular 14sp</li>
+</ul>
+
+<p>Some TVs have strong sharpness and contrast settings as their defaults. These picture
+  settings make thin and light typefaces look jagged and make the text difficult for people to read.
+  Therefore you should avoid thin or light typefaces on TV.</p>
+
+<h2>Text</h2>
+
+<p>Use text in TV apps sparingly. The position of users relative to a TV screen
+  (typically about 10 feet away) makes it harder for users to read text. Users also don't expect to
+  read much in a TV environment. Follow these tips for the best handling of text in your app:</p>
+
+<ul>
+  <li>Break text into small chunks that users can quickly scan.</li>
+  <li>Use light text on a dark background. This style is easier to read on a TV.</li>
+  <li>Avoid lightweight fonts or fonts that have both very narrow and very broad
+      strokes. Use simple sans-serif fonts and anti-aliasing to increase readability.</li>
+  <li>Use layout-relative sizing rather than absolute sizing, and density-independent
+      pixel units instead of absolute pixel units.</li>
+</ul>
diff --git a/docs/html/preview/tv/games/index.jd b/docs/html/preview/tv/games/index.jd
new file mode 100644
index 0000000..763eada
--- /dev/null
+++ b/docs/html/preview/tv/games/index.jd
@@ -0,0 +1,152 @@
+ikpage.title=Games on TV
+page.tags="controller"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#display">Display</li></a></li>
+    <li><a href="#control">Input Devices</li></a></li>
+    <li><a href="#manifest">Manifest</li></a></li>
+    <li><a href="#gpgs">Google Play Game Services</li></a></li>
+    <li><a href="#web">Web</a></li>
+  </ol>
+</div>
+</div>
+
+<p>The television screen presents a number of considerations that may be new to mobile-game
+developers. These areas include its large size, its control scheme, and the fact that all
+players are viewing it simultaneously.</p>
+
+
+<h2 id=display>Display</h2>
+<p>The two main things to keep in mind when developing games for the TV screen are its nature as a
+shared display and the need to design your game for a landscape orientation.</p>
+<h3>Shared display</h3>
+<p>A living-room TV poses design challenges for multiplayer games, in that all players can see
+everything. This issue is especially relevant to games (such as card games or strategy games) that
+rely on each player’s possession of hidden information.</p>
+<p>Some mechanisms you can implement to address the problem of one player’s eavesdropping
+on another’s information are:</p>
+<ul>
+<li>A blinder on the screen to help conceal information. For example, in a
+turn-based game like a word or card game, one player at a time might view the display. When the
+player finishes a move, the game allows him or her to cover the screen with a blinder that
+blocks anyone from viewing secret information. When the next player begins a turn, the blinder
+opens to reveal his or her own information.</li>
+<li>A companion app, running on a phone or tablet, can enable a player to conceal
+information.</li>
+</ul>
+<h3>Landscape display</h3>
+<p>A TV is always sideways: You can’t turn it, and there is no
+portrait orientation. Always design your TV games to be displayed in landscape
+mode.</p>
+
+<h2 id="control">Input Devices</h2>
+<p>TVs don't have touch interfaces, so it's even more important to get your controls right and make
+ sure that players find them intuitive and fun to use. The separation of controller from device also
+introduces some other issues to pay attention to, like keeping track of multiple players'
+controllers, and handling disconnects gracefully.</p>
+<h3>D-pad</h3>
+<p>Plan your control scheme around a directional pad (D-pad) control, since this control set is the
+default for Android TV devices. The player needs to be able to use a D-Pad in all aspects of the
+game&ndash;not just controlling core
+gameplay, but also navigating menus and ads. For this reason, you should also ensure that your
+Android TV game does not refer to a touch interface: for example, an Android TV game cannot tell a
+player to <strong>Tap to skip</strong>.</p>
+<p>How you shape the player's interaction with the controller can be key to achieving a great user
+experience:
+  <ul>
+  <p><li><strong>Communicate Controller Requirements up Front</strong> - Use your Play Store description to communicate to the player any expectations about
+controllers. If a game is better suited to a gamepad with a joystick than one with only a D-pad,
+make this fact clear. A player who uses an ill-suited controller for a game is likely to have a
+subpar experience&ndash;and penalize your game in the ratings.</p>
+  <p><li><strong>Use Consistent Button Mapping</strong> - Intuitive and flexible button mapping is key to a good user experience. For example,
+you can adhere to accepted custom by using the A button to <code>Accept</code>, and the B button to
+<code>Cancel</code>. You can also offer flexibility in the form of remappability. For more
+information on button mapping, see <a
+href="http://developer.android.com/training/game-controllers/controller-input.html">Handling
+Controller Actions</a>.</p>
+  <p><li><strong>Detect Controller Capabilities and Adjust Accordingly</strong> - Query the controller about its capabilities in order to optimize the match between
+controller and game. For example, you may intend for a player to steer an object by waving the
+controller in the air. If a player's controller lacks accelerometer and gyroscope hardware, however,
+waving will not work. When, however, your game queries the controller and discovers that motion detection
+is not supported, it can switch over to an alternative, available control scheme.
+For more information on querying controller capabilities, see <a
+href="http://developer.android.com/training/game-controllers/compatibility.html">Supporting
+Controllers Across Android Versions</a>.</p>
+  </ul>
+<h3>Back-button behavior</h3>
+<p>The Back button should never act as a toggle. For example, do not use it to both open and close
+a menu. It should only navigate backward, breadcrumb-style, through the previous screens the player has
+been on. For example: Game play &gt; Game pause screen &gt; Game
+main screen &gt; Android home screen.</p>
+<p>Since the Back button should only perform linear (backward) navigation, you may use the
+back button to leave an in-game menu (opened by a different button) and return to gameplay. For
+more information about design for navigation, see <a
+href="http://developer.android.com/design/patterns/navigation.html">Navigation with Back and
+Up</a>. To learn about implementation, refer to <a
+href="http://developer.android.com/training/implementing-navigation/temporal.html">Providing Proper
+Back Navigation</a>. </p>
+<h3>Handling multiple controllers</h3>
+<p>When multiple players are playing a game, each with his or her own controller, it is important
+to map each player-controller pair. For information on how to implement controller-number
+identification, see <a href="http://developer.android.com/reference/android/view/InputDevice.html
+#getControllerNumber(">Input Devices</a>) on the Android developer site.</p>
+<h3>Handling disconnects</h3>
+<p>When a controller is disconnected in the middle of gameplay, the game should pause, and a dialog
+should appear prompting the disconnected player to reconnect his or her controller.</p>
+<p>The dialog should also offer troubleshooting tips (for example, a pop-up dialog telling the player to
+"Check your Bluetooth connection"). For more information on implementing input-device support, see <a
+href="http://developer.android.com/training/game-controllers/controller-input.html">Supporting Game
+Controllers"</a>. Specific information about Bluetooth connections is at <a
+href="http://developer.android.com/guide/topics/connectivity/bluetooth.html">Bluetooth</a>.</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>Games are displayed in a separate row from regular apps in the launcher. Android TV uses the
+<code>android:isGame</code> flag to differentiate games from non-game apps. You can assign it a
+value of either <code>true</code> or <code>false</code>. For example:</p>
+<pre class="fragment">&lt;application&gt;
+ ...
+&lt; android:isGame=["true" | "false"] &gt;
+ ...
+&lt;/application&gt;</pre>
+
+<h2 id="gpgs">Google Play Game Services</h2>
+<p>If your game integrates Google Play Game Services, you should keep in mind a number of
+considerations pertaining to achievements, sign-on, saving games, and multiplayer play.</p>
+<h3>Achievements</h3>
+<p>Your game should include at least five (earnable) achievements. Only a user controlling gameplay
+from a supported input device should be able to earn achievements. For more information on
+Achievements and how to implement them, see <a
+href="https://developers.google.com/games/services/android/achievements">Achievements in
+Android</a>.</p>
+<h3>Sign-in</h3>
+<p>Your game should attempt to sign the user in on launch. If the player declines sign-in several
+times in a row, your game should stop asking. Learn more about sign-in at <a
+href="https://developers.google.com/games/services/training/signin">Implementing Sign-in on
+Android</a>.</p>
+<h3>Saving</h3>
+<p>We highly recommend using Play Services <a
+href="https://developers.google.com/games/services/common/concepts/cloudsave">Cloud Save</a> to
+store your game save. Your game should bind game saves to a specific Google account, so as to be
+uniquely identifiable even across devices: Whether the player is using a handset or a TV, the game
+should be able to pull the same game-save information from his or her account.</p>
+<p>You should also provide an option in your game's UI to allow the player to delete locally and
+cloud-stored data. You might put the option in the game's <code>Settings</code> screen. For
+specifics on implementing Cloud Save, see <a
+href="https://developers.google.com/games/services/android/cloudsave">Cloud Save in Android</a>.</p>
+<h3>Multiplayer experience</h3>
+<p>A game offering a multiplayer experience must allow at least two players to enter a room. For
+further information on multiplayer games in Android, see the <a
+href="https://developers.google.com/games/services/android/realtimeMultiplayer">Real-time
+Multiplayer</a> and <a href="">Turn-based Multiplayer</a> documentation on the Android developer
+site.</p>
+
+<h2 id="web">Web</h2>
+<p>We discourage including web browsing in games for Android TV. The television set is not well-suited for browsing,, either in terms of display or control scheme.</p>
+<p class="note"><strong>Note:</strong> You can use the {@link android.webkit.WebView} class for logins to services like Google+ and
+Facebook. </p>
+
diff --git a/docs/html/preview/tv/images/android-tv-remote.png b/docs/html/preview/tv/images/android-tv-remote.png
new file mode 100644
index 0000000..d15fbc5
--- /dev/null
+++ b/docs/html/preview/tv/images/android-tv-remote.png
Binary files differ
diff --git a/docs/html/preview/tv/images/home-recommendations.png b/docs/html/preview/tv/images/home-recommendations.png
new file mode 100644
index 0000000..ef97145
--- /dev/null
+++ b/docs/html/preview/tv/images/home-recommendations.png
Binary files differ
diff --git a/docs/html/preview/tv/index.jd b/docs/html/preview/tv/index.jd
new file mode 100644
index 0000000..dd35908
--- /dev/null
+++ b/docs/html/preview/tv/index.jd
@@ -0,0 +1,22 @@
+page.title=Android TV Apps
+
+@jd:body
+
+<p>Android offers a rich user experience that's optimized for apps running on large screen
+  devices, such as high-definition televisions. Apps on TV offer new opportunities to
+  delight your users from the comfort of their couch.</p>
+
+<p>This guide helps you build apps for TV devices, including:</p>
+
+<ul>
+  <li>How to set up your development environment</li>
+  <li>How to build user interfaces for TV</li>
+  <li>Guidelines for building games for TV</li>
+</ul>
+
+<p>Prepare your app for its big screen debut!</p>
+
+<p>
+  <strong><a href="{@docRoot}preview/tv/start/index.html">Get Started &gt;</a></strong>
+</p>
+
diff --git a/docs/html/preview/tv/start/hardware-features.jd b/docs/html/preview/tv/start/hardware-features.jd
new file mode 100644
index 0000000..ddec496
--- /dev/null
+++ b/docs/html/preview/tv/start/hardware-features.jd
@@ -0,0 +1,183 @@
+page.title=Hardware Features on TV
+page.tags="unsupported"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#unsupported-features">Unsupported Hardware Features</a></li>
+    <li><a href="#workaround-features">Handling Unsupported Features</a></li>
+    <li><a href="#check-features">Checking Available Features</a>
+      <ol>
+        <li><a href="#no-touchscreen">Touch screen</a></li>
+        <li><a href="#no-camera">Camera</a></li>
+        <li><a href="#no-gps">GPS</a></li>
+      </ol>
+
+    </li>
+  </ol>
+</div>
+</div>
+
+<p>TVs do not have some of the hardware features found on other Android devices.
+Touch screens, cameras, and GPS receivers are some of the most commonly used hardware features
+which are typically not available on a TV. When you build an app for TV, you must carefully
+consider if your app can handle not having these features and, if necessary, work around them.</p>
+
+<p>This guide discusses the hardware features not available on TV devices and shows you how to
+work around those limitations in your app. For more information on filtering and declaring
+features in the manifest, see the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">uses-feature</a> guide.</p>
+
+
+<h2 id="unsupported-features">Unsupported Hardware Features</h2>
+
+<p>TVs have a different purpose from other devices, and so they do not have hardware
+features that other Android-powered devices often have. For this reason, the Android system
+does not support the following features for a TV device:
+
+<table>
+<tr>
+<th>Hardware</th>
+<th>Android feature descriptor</th>
+</tr>
+<tr>
+<td>Camera</td>
+<td>android.hardware.camera</td>
+</tr>
+<tr>
+<td>GPS</td>
+<td>android.hardware.location.gps</td>
+</tr>
+<tr>
+<td>Microphone</td>
+<td>android.hardware.microphone</td>
+</tr>
+<tr>
+<td>Near Field Communications (NFC)</td>
+<td>android.hardware.nfc</td>
+</tr>
+<tr>
+<td>Telephony</td>
+<td>android.hardware.telephony</td>
+</tr>
+<tr>
+<td>Touchscreen</td>
+<td>android.hardware.touchscreen</td>
+</tr>
+</table>
+</p>
+
+
+<h2 id="check-features">Checking Available Features</h2>
+
+<p>To check if a feature is available at runtime, call {@link
+  android.content.pm.PackageManager#hasSystemFeature(String)}. This method takes a single string
+  argument that specifies the feature you want to check. For example, to check for a touch screen,
+  use {@link android.content.pm.PackageManager#hasSystemFeature(String)} with the argument
+  {@link android.content.pm.PackageManager#FEATURE_TOUCHSCREEN}.</p>
+
+<p>The following code example demonstrates how to detect the availability of a hardware features
+  at runtime:</p>
+
+<pre>
+// Check if the telephony hardware feature is available.
+if (getPackageManager().hasSystemFeature("android.hardware.telephony")) {
+    Log.d("Mobile Test", "Running on phone");
+// Check if android.hardware.touchscreen feature is available.
+} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) {
+    Log.d("Tablet Test", "Running on devices that don't support telephony but "+
+            "do have a touch screen.");
+} else {
+    Log.d("TV Test", "Running on a TV!");
+}
+</pre>
+
+<p class="note">
+  <strong>Note:</strong> You can also use the {@link android.app.UiModeManager#getCurrentModeType
+  UiModeManager.getCurrentModeType()} method to detect the current platform type. For TV devices,
+  this method returns a value of {@link android.content.res.Configuration#UI_MODE_TYPE_TELEVISION
+  Configuration.UI_MODE_TYPE_TELEVISION}.
+</p>
+
+
+<h2 id="workaround-features">Handling Unsupported Features</h2>
+
+<p>Depending on the design and functionality of your app, you may be able to work around certain
+  hardware features being unavailable. This section discusses how to work around specific hardware
+  features.</p>
+
+
+<h3 id="no-touchscreen">Touch screen</h3>
+
+<p>Android doesn't support touch screen interaction for TV devices, since most TVs don't have touch
+  screens, and using a touch screen is not consistent with a viewing environment where the user is
+  seated 10 feet away from the display.</p>
+
+<p>On TV devices, you should work around this limitation by supporting navigation using a directional
+  pad (D-pad) on TV remote control. For more information on properly supporting navigation using
+  TV-friendly controls, see <a href="{@docRoot}preview/tv/ui/navigation.html">Navigation for
+  TV</a>.</p>
+
+<p>You can explicitly declare if your application requires (or does not require) a touch screen
+  by including the following entry in your manifest:</p>
+
+<pre>
+&lt;uses-feature android:name="android.hardware.touchscreen"
+        android:required="false"/&gt;
+</pre>
+
+
+<h3 id="no-camera">Camera</h3>
+
+<p>Although a TV typically does not have a camera, you can still provide a photography-related
+  application on a TV. For example, if you have an app that takes, views and edits photos, you can
+  disable its picture-taking functionality for TVs and still allow users to view and even edit
+  photos. If you decide that you want to enable your camera-related application to work on a
+  TV device without a camera, you can add an attribute to your app manifest declaring that
+  a camera is not required by your app:</p>
+
+<pre>
+&lt;uses-feature android:name="android.hardware.camera" android:required="false" /&gt;
+</pre>
+
+<p>If you enable your application to run without a camera, you should add code to your application
+that detects if the camera feature is available and makes adjustments to the operation of your app.
+The following code example demonstrates how to detect the presence of a camera:</p>
+
+<pre>
+// Check if the camera hardware feature is available.
+if (getPackageManager().hasSystemFeature("android.hardware.camera")) {
+    Log.d("Camera test", "Camera available!");
+} else {
+    Log.d("Camera test", "No camera available. View and edit features only.");
+}
+</pre>
+
+
+<h3 id="no-gps">GPS</h3>
+
+<p>TVs are stationary, indoor devices, and do not have built-in global positioning system (GPS)
+  receivers. If your application uses location information, you can still allow users to search
+  for a location, or use a static location provider such as a zip code configured during the
+  TV device setup.</p>
+
+<pre>
+LocationManager locationManager = (LocationManager) this.getSystemService(
+        Context.LOCATION_SERVICE);
+Location location = locationManager.getLastKnownLocation("static");
+Geocoder geocoder = new Geocoder(this);
+Address address = null;
+
+try {
+  address = geocoder.getFromLocation(location.getLatitude(),
+          location.getLongitude(), 1).get(0);
+  Log.d("Zip code", address.getPostalCode());
+
+} catch (IOException e) {
+  Log.e(TAG, "Geocoder error", e);
+}
+</pre>
+
diff --git a/docs/html/preview/tv/start/index.jd b/docs/html/preview/tv/start/index.jd
new file mode 100644
index 0000000..b75fee0
--- /dev/null
+++ b/docs/html/preview/tv/start/index.jd
@@ -0,0 +1,231 @@
+page.title=Get Started with TV Apps
+page.tags="leanback","recyclerview","launcher"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#prerequisites">Prerequisites</a></li>
+    <li><a href="#dev-project">Setup a TV Project</a>
+      <ul>
+        <li><a href="#tv-activity">Create a TV Activity</a></li>
+        <li><a href="#tv-libraries">Add TV Support Libraries</a></li>
+      </ul>
+    </li>
+    <li><a href="#build-it">Build TV Apps</a></li>
+    <li><a href="#run">Run TV Apps</a></li>
+
+  </ol>
+</div>
+</div>
+
+<p>This guide describes how to prepare your development environment and projects for building
+  TV apps, including updating your existing app to run on TV devices.</p>
+
+
+<h2 id="prerequisites">Prerequisites</h2>
+
+<p>Before you begin setting up to build apps for TV, you must:</p>
+
+<ul>
+  <li><strong><a href="{@docRoot}preview/setup-sdk.html">
+    Set up the Preview SDK</a></strong>
+    <br>
+    The preview SDK provides the developer tools needed to build and test apps for TV.
+  </li>
+  <li><strong><a href="{@docRoot}preview/setup-sdk.html#project">
+    Create a Preview SDK Project</a></strong>
+    <br>
+    In order to access new APIs for TV devices, you must create a project that targets the preview
+    release level or modify an existing project to target the preview release.
+  </li>
+</ul>
+
+
+<h2 id="dev-project">Set up a TV Project</h2>
+
+<p>TV apps use the same structure as those for phones and tablets. This means you can modify
+  your existing apps to also run on TV devices or create new apps based on what you already know
+  about building apps for Android. This section discusses how to modify an existing app, or create a
+  new one, to run on TV devices.</p>
+
+<p>These are the main steps to creating an app that runs on TV devices. Only the first
+  is required:</p>
+
+<ul>
+  <li><strong>Activity for TV</strong> - (Required) In your application manifest, you must
+    declare an activity that is intended to run on TV devices.</li>
+  <li><strong>TV Support Libraries</strong> - (Optional) There are several Support Libraries
+    available for TV devices that provide widgets for building user interfaces.</li>
+</ul>
+
+
+<h3 id="tv-activity">Create a TV Activity</h3>
+
+<p>An application intended to run on TV devices must declare a launcher activity for TV
+  in its manifest using a {@code android.intent.category.LEANBACK_LAUNCHER} intent filter.
+  This filter identifies your app as being built for TV, enabling it to be displayed in the
+  Google Play store app running on TV devices. Declaring this intent also identifies which activity
+  in your app should be launched when a user selects its icon on the TV home screen.</p>
+
+<p class="caution">
+  <strong>Caution:</strong> If you do not include the {@code LEANBACK_LAUNCHER} intent filter in
+  your app, it is not visible to users running the Google Play store on TV devices. Also, if your
+  app does not have this filter when you load it onto a TV device using developer tools, the app
+  does not appear in the TV user interface.
+</p>
+
+<p>The following code snippet shows how to include this intent filter in your manifest:</p>
+
+<pre>
+&lt;application&gt;
+  ...
+  &lt;activity
+    android:name=&quot;com.example.android.MainActivity&quot;
+    android:label=&quot;@string/app_name&quot; &gt;
+
+    &lt;intent-filter&gt;
+      &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
+      &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
+    &lt;/intent-filter&gt;
+  &lt;/activity&gt;
+
+  &lt;activity
+    android:name=&quot;com.example.android.<strong>TvActivity</strong>&quot;
+    android:label=&quot;&#64;string/app_name&quot;
+    android:theme=&quot;&#64;android:style/Theme.Leanback&quot;&gt;
+
+    &lt;intent-filter&gt;
+      &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
+      &lt;category android:name="<strong>android.intent.category.LEANBACK_LAUNCHER</strong>" /&gt;
+    &lt;/intent-filter&gt;
+
+  &lt;/activity&gt;
+&lt;/application&gt;
+</pre>
+
+<p>The second activity manifest entry in the example above specifies that activity as
+  the main one when your app launches on an TV device.</p>
+
+<p>If you have an existing app that you are modifying for TV use, your app should not use the same
+  activity layout for TV that it does for phones and tablets. The user interface of your TV app (or
+  TV portion of your existing app) should provide a simpler interface that can be easily navigated
+  using a remote control from a couch. For guidelines on designing an app for TV, see the
+  <a href="{@docRoot}design/tv/index.html">TV Design</a> guide.  For more instructions on
+  developing a user interface appropriate to TV, see the
+  <a href="{@docRoot}preview/tv/ui/index.html">TV User Interface</a> guide.
+</p>
+
+
+<h3 id="tv-libraries">Add TV Support Libraries</h3>
+
+<p>The Preview SDK includes support libraries that are intended for use with TV apps. These
+  libraries provide APIs and user interface widgets for use on TV devices. The libraries are
+  located in the {@code &lt;sdk&gt;/extras/android/support/} directory where you installed the
+  Preview SDK. Here is a list of the libraries and their general purpose:</p>
+
+<ul>
+  <li><strong>v17 leanback library</strong> - Provides user interface widgets for TV, including
+    {@code BrowseFragment}, {@code DetailsFragment}, and {@code SearchFragment}.
+    <ul>
+      <li>SDK location: {@code &lt;sdk&gt;/extras/android/support/v17/leanback}</li>
+      <li>Gradle dependency: {@code com.android.support:leanback-v17:20.0.+}</li>
+      <li>Contains resources: Yes</li>
+    </ul>
+  </li>
+  <li><strong>v7 recyclerview library</strong> - Provides classes for managing display of long
+  lists in a memory efficient manner. Several classes in the v17 leanback library depend on the
+  classes in this library.
+    <ul>
+      <li>SDK location: {@code &lt;sdk&gt;/extras/android/support/v7/recyclerview}</li>
+      <li>Gradle dependency: {@code com.android.support:recyclerview-v7:20.0.+}</li>
+      <li>Contains resources: No</li>
+    </ul>
+  </li>
+</ul>
+
+<p class="note">
+  <strong>Note:</strong> You are not required to use these support libraries for your TV app.
+  However, we strongly recommend using them, particularly for apps that provide a media catalog
+  browsing interface.
+</p>
+
+<p>If you decide to use the v17 leanback library for your app, you should note that it is
+  dependent on the
+  <a href="{@docRoot}tools/support-library/features.html#v4">v4 support library</a>. This means
+  that apps that use the leanback support library should include all of these support
+  libraries:</p>
+
+<ul>
+  <li>v17 leanback support library</li>
+  <li>v7 recyclerview support library</li>
+  <li>v4 support library</li>
+</ul>
+
+<p>The v17 leanback library contain resources, which requires
+  you to take specific steps to include it in app projects. For instructions on
+  importing a support library with resources, see
+  <a href="http://developer.android.com/tools/support-library/setup.html#libs-with-res">
+  Support Library Setup</a>.
+</p>
+
+
+<h2 id="build-it">Build TV Apps</h2>
+
+<p>After you have completed the steps described above, it's time to start building apps for
+  the big screen! Check out these additional topics to help you build your app for TV:
+
+<ul>
+  <li><a href="{@docRoot}preview/tv/ui/index.html">User Interface</a> - The user interface of
+    TV devices is different from those of other Android devices. See this topic to find out how
+    to build TV user interfaces and to learn about the widgets provided to simplify that task.
+  </li>
+  <li><a href="{@docRoot}preview/tv/games/index.html">Games for TV</a> - TV devices are great
+    platforms for games. See this topic for information on building great game experiences for
+    TV.</li>
+  <li><a href="{@docRoot}preview/tv/start/hardware-features.html">Hardware features</a> - TV
+    devices do not contain hardware features normally found on other Android devices. See this
+    topic for information on unsupported hardware features and what to do about them.
+  </li>
+</ul>
+
+
+<h2 id="run">Run TV Apps</h2>
+
+<p>Running your app is an important part of the development process. The AVD Manager in the
+  Android SDK provides the device definitions that allows you to create virtual TV devices for
+  running and testing your applications.</p>
+
+<p>To create an virtual TV device:</p>
+
+<ol>
+  <li>Start the AVD Manager. For more information, see the
+    <a href="{@docRoot}tools/help/avd-manager.html">AVD Manager</a> help.</li>
+  <li>In the AVD Manager dialog, click the <strong>Device Definitions</strong> tab.</li>
+  <li>Select one of the Android TV device definitions, such as
+    <strong>Large Android TV</strong>, and click <strong>Create AVD</strong>.</li>
+  <li>Select the emulator options and click <strong>OK</strong> to create the AVD.
+    <p class="note">
+      <strong>Note:</strong> For best performance of the TV emulator device, enable the <strong>Use
+      Host GPU</strong> option and CPU platform image that supports hardware acceleration. For
+      more information on hardware acceleration of the emulator, see
+      <a href="{@docRoot}tools/devices/emulator.html#acceleration">Using the Emulator</a>.
+    </p>
+  </li>
+</ol>
+
+<p>To test your application on the virtual TV device:</p>
+
+<ol>
+  <li>Compile your TV application in your development environment.</li>
+  <li>Run the application from your development environment and choose the TV virtual device as
+  the target.</li>
+</ol>
+
+<p>For more information about using emulators see, <a href="{@docRoot}tools/devices/emulator.html">
+Using the Emulator</a>. For more information about deploying apps to emulators from
+Eclipse with ADT, see <a href="{@docRoot}http://developer.android.com/tools/building/building-eclipse.html">
+Building and Running from Eclipse with ADT</a>.</p>
+
diff --git a/docs/html/preview/tv/ui/browse.jd b/docs/html/preview/tv/ui/browse.jd
new file mode 100644
index 0000000..d6b97c1
--- /dev/null
+++ b/docs/html/preview/tv/ui/browse.jd
@@ -0,0 +1,198 @@
+page.title=BrowseFragment
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#layout">Media Browse Layout</a></li>
+    <li><a href="#lists">Displaying Media Lists</a></li>
+    <li><a href="#background">Updating the Background</a></li>
+  </ol>
+
+</div>
+</div>
+
+<p>The <a href="{@docRoot}preview/tv/start/index.html#tv-libraries">Leanback support library</a>
+  provides several APIs for displaying and browsing media catalogs
+  on the TV devices. This guide discusses how to use the classes provided by this library to
+  implement a user interface for browsing music or videos from your app's media catalog.</p>
+
+
+<h2 id="layout">Media Browse Layout</h2>
+
+<p>The {@code BrowseFragment} class in the Leanback support library allows you to create a primary
+  layout for browsing categories and rows of media items with a minimum of code. The following
+  example shows how to create a layout that contains a {@code BrowseFragment}:</p>
+
+<pre>
+&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width=&quot;match_parent&quot;
+  android:layout_height=&quot;match_parent&quot;
+  android:orientation=&quot;vertical&quot;
+  &gt;
+
+  &lt;fragment
+      <strong>android:name="android.support.v17.leanback.app.BrowseFragment"</strong>
+      android:id=&quot;@+id/browse_fragment&quot;
+      android:layout_width=&quot;match_parent&quot;
+      android:layout_height=&quot;match_parent&quot;
+      /&gt;
+&lt;/LinearLayout&gt;
+</pre>
+
+<p>In order to work with this layout in an activity, retrieve the {@code BrowseFragment} element
+  from the layout. Use the methods in {@code BrowseFragment} to set display parameters such as the
+  icon, title and whether category headers are enabled. The following code sample demonstrates how
+  to set the layout parameters for a {@code BrowseFragment} in a layout:</p>
+
+<pre>
+public class BrowseMediaActivity extends Activity {
+
+    public static final String TAG ="BrowseActivity";
+
+    protected BrowseFragment mBrowseFragment;
+
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.browse_fragment);
+
+        final FragmentManager fragmentManager = getFragmentManager();
+        <strong>mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById(
+                R.id.browse_fragment);</strong>
+
+        // Set display parameters for the BrowseFragment
+        mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
+        mBrowseFragment.setTitle(getString(R.string.app_name));
+        mBrowseFragment.setBadgeDrawable(getResources().getDrawable(R.drawable.ic_launcher));
+        mBrowseFragment.setBrowseParams(params);
+
+    }
+}
+</pre>
+
+
+<h2 id="lists">Displaying Media Lists</h2>
+
+<p>The {@code BrowseFragment} allows you to define and display browsable media content categories and
+  media items from a media catalog using adapters and presenters. Adapters enable you to connect to
+  local or online data sources that contain your media catalog information. Presenter classes hold
+  data about media items and provide layout information for displaying an item on screen.</p>
+
+<p>The following example code shows an implementation of a presenter for displaying string
+  data:</p>
+
+<pre>
+public class StringPresenter extends Presenter {
+    private static final String TAG = "StringPresenter";
+
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        TextView textView = new TextView(parent.getContext());
+        textView.setFocusable(true);
+        textView.setFocusableInTouchMode(true);
+        textView.setBackground(
+                parent.getContext().getResources().getDrawable(R.drawable.text_bg));
+        return new ViewHolder(textView);
+    }
+
+    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+        ((TextView) viewHolder.view).setText(item.toString());
+    }
+
+    public void onUnbindViewHolder(ViewHolder viewHolder) {
+        // no op
+    }
+}
+</pre>
+
+<p>Once you have constructed a presenter class for your media items, you can build and attach an
+  adapter to the {@code BrowseFragment} to display those items on screen for browsing by the user. The
+  following example code demonstrates how to construct an adapter to display categories and items
+  in those categories using the StringPresenter class shown in the previous code example:</p>
+
+<pre>
+private ArrayObjectAdapter mRowsAdapter;
+private static final int NUM_ROWS = 4;
+
+&#64;Override
+protected void onCreate(Bundle savedInstanceState) {
+    ...
+
+    buildRowsAdapter();
+}
+
+private void buildRowsAdapter() {
+    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+
+    for (int i = 0; i &lt; NUM_ROWS; ++i) {
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
+                new StringPresenter());
+        listRowAdapter.add("Media Item 1");
+        listRowAdapter.add("Media Item 2");
+        listRowAdapter.add("Media Item 3");
+        HeaderItem header = new HeaderItem(i, "Category " + i, null);
+        mRowsAdapter.add(new ListRow(header, listRowAdapter));
+    }
+
+    mBrowseFragment.setAdapter(mRowsAdapter);
+}
+</pre>
+
+<p>This example shows a static implementation of the adapters. A typical media browsing
+  application uses data from an online database or web service. For an example of a browsing
+  application that uses data retrieved from the web, see the
+  <a href="http://github.com/googlesamples/androidtv-leanback">Android TV</a> sample app.</p>
+
+
+<h2 id="background">Updating the Background</h2>
+
+<p>In order to add visual interest to a media-browsing app on TV, you can update the background
+  image as users browse through content. This technique can make interaction with your app feel more
+  cinematic and enjoyable for users.</p>
+
+<p>The Leanback support library provides a {@code BackgroundManager} class for changing the
+  background of your TV app activity. The following example shows how to create a simple method
+  for updating the background within your TV app activity:</p>
+
+<pre>
+protected void updateBackground(Drawable drawable) {
+    BackgroundManager.getInstance(this).setDrawable(drawable);
+}
+</pre>
+
+<p>Many of the existing media-browse apps automatically update the background as the user
+  navigates through media listings. In order to do this, you can set up a selection listener to
+  automatically update the background based on the user's current selection. The following example
+  shows you how to set up an {@code OnItemSelectedListener}
+  class to catch selection events and update the background:</p>
+
+<pre>
+protected void clearBackground() {
+    BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
+}
+
+protected OnItemSelectedListener getDefaultItemSelectedListener() {
+    return new OnItemSelectedListener() {
+        &#64;Override
+        public void onItemSelected(Object item, Row row) {
+            if (item instanceof Movie ) {
+                URI uri = ((Movie)item).getBackdropURI();
+                updateBackground(uri);
+            } else {
+                clearBackground();
+            }
+        }
+    };
+}
+</pre>
+
+<p class="note">
+  <strong>Note:</strong> The implementation above is a simple example shown for purposes of
+  illustration. When creating this function in your own app, you should consider running the
+  background update action in a separate thread for better performance. In addition, if you are
+  planning on updating the background in response to users scrolling through items, consider adding
+  a time to delay a background image update until the user settles on an item. This technique avoids
+  excessive background image updates.
+</p>
diff --git a/docs/html/preview/tv/ui/details.jd b/docs/html/preview/tv/ui/details.jd
new file mode 100644
index 0000000..8b8fa8b5
--- /dev/null
+++ b/docs/html/preview/tv/ui/details.jd
@@ -0,0 +1,214 @@
+page.title=DetailFragment
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#details-presenter">Build a Details Presenter</a></li>
+    <li><a href="#details-fragment">Extend the Details Fragment</a>
+      <li><a href="#activity">Creating a Details Activity</a></li>
+      <li><a href="#item-listener">Listener for Clicked Items</a></li>
+    </li>
+  </ol>
+</div>
+</div>
+
+<p>The media browsing interface classes provided by the
+  <a href="{@docRoot}preview/tv/start/index.html#tv-libraries">Leanback support library</a>
+  include classes for displaying additional information about a media item, such as a description
+  or reviews, and for taking action on that item, such as purchasing it or playing its content. This
+  section discusses how to create a presenter class for media item details and extend the
+  {@code DetailsFragment} class to implement a details view for a media item when it
+  is selected by a user.
+</p>
+
+<p class="note">
+  <strong>Note:</strong> The implementation example shown here uses an additional activity to
+  contain the {@code DetailsFragment}. However, it is possible to avoid creating a second activity
+  by replacing the current {@code BrowseFragment} with a {@code DetailsFragment} within the <em>same</em>
+  activity using fragment transactions. For more information on using fragment transactions, see the
+  <a href="{@docRoot}training/basics/fragments/fragment-ui.html#Replace">Building a Dynamic
+    UI with Fragments</a> training.
+</p>
+
+
+<h2 id="details-presenter">Build a Details Presenter</h2>
+
+<p>In the media browsing framework provided for by the leanback support library, you use
+  presenter objects to control the display of data on screen, including media item details. The
+  framework provides the {@code AbstractDetailsDescriptionPresenter} class for this purpose, which
+  is a nearly complete implementation of the presenter for media item details. All you have to do is
+  implement the {@code onBindDescription()} method to bind the view fields to your data objects, as shown in
+  the following code sample:</p>
+
+<pre>
+public class DetailsDescriptionPresenter
+        extends AbstractDetailsDescriptionPresenter {
+
+    &#64;Override
+    protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
+        MyMediaItemDetails details = (MyMediaItemDetails) itemData;
+        // In a production app, the itemData object contains the information
+        // needed to display details for the media item:
+        // viewHolder.getTitle().setText(details.getShortTitle());
+
+        // Here we provide static data for testing purposes:
+        viewHolder.getTitle().setText(itemData.toString());
+        viewHolder.getSubtitle().setText("2014   Drama   TV-14");
+        viewHolder.getBody().setText("Lorem ipsum dolor sit amet, consectetur "
+                + "adipisicing elit, sed do eiusmod tempor incididunt ut labore "
+                + " et dolore magna aliqua. Ut enim ad minim veniam, quis "
+                + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
+                + "commodo consequat.");
+    }
+}
+</pre>
+
+
+<h2 id="details-fragment">Extend the Details Fragment</h2>
+
+<p>When you use the {@code DetailsFragment} class for displaying your media item details, you
+  extend that class to provide additional content such as a preview image and actions for the media
+  item. You can also provide additional content, such as a list of related media items.</p>
+
+<p>The following example code demonstrates how to use the presenter class you created in the
+  previous section, add a preview image and actions for the media item being viewed. This example
+  also shows the addition of a related media items row, which appears below the details listing.</p>
+
+<pre>
+public class MediaItemDetailsFragment extends DetailsFragment {
+    private static final String TAG = "MediaItemDetailsFragment";
+    private ArrayObjectAdapter mRowsAdapter;
+
+    &#64;Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        buildDetails();
+    }
+
+    private void buildDetails() {
+        ClassPresenterSelector selector = new ClassPresenterSelector();
+        // Attach your media item details presenter to the row presenter:
+        DetailsOverviewRowPresenter rowPresenter =
+            new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+
+        selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
+        selector.addClassPresenter(ListRow.class,
+                new ListRowPresenter());
+        mRowsAdapter = new ArrayObjectAdapter(selector);
+
+        Resources res = getActivity().getResources();
+        DetailsOverviewRow detailsOverview = new DetailsOverviewRow(
+                "Media Item Details");
+
+        // Add images and action buttons to the details view
+        detailsOverview.setImageDrawable(res.getDrawable(R.drawable.jelly_beans));
+        detailsOverview.addAction(new Action(1, "Buy $9.99"));
+        detailsOverview.addAction(new Action(2, "Rent $2.99"));
+        mRowsAdapter.add(detailsOverview);
+
+        // Add a Related items row
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
+                new StringPresenter());
+        listRowAdapter.add("Media Item 1");
+        listRowAdapter.add("Media Item 2");
+        listRowAdapter.add("Media Item 3");
+        HeaderItem header = new HeaderItem(0, "Related Items", null);
+        mRowsAdapter.add(new ListRow(header, listRowAdapter));
+
+        setAdapter(mRowsAdapter);
+    }
+}
+</pre>
+
+
+<h3 id="activity">Creating a Details Activity</h3>
+
+<p>Fragments such as the {@code DetailsFragment} must be contained within an activity in order
+  to be used for display. Creating an activity for your details view, separate from the browse
+  activity, enables you to invoke your details view using an Intent. This section explains how to
+  build an activity to contain your implementation of the detail view for your media items.</p>
+
+<p>Start creating the details activity by building a layout that references your implementation
+  of the {@code DetailsFragment}:</p>
+
+<pre>
+&lt;!-- file: res/layout/details.xml --&gt;
+
+&lt;fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    <strong>android:name="com.example.android.mediabrowser.MediaItemDetailsFragment"</strong>
+    android:id="&#64;+id/details_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/&gt;
+</pre>
+
+<p>Next, create an activity class that uses the layout shown in the previous code example:</p>
+
+<pre>
+public class DetailsActivity extends Activity
+{
+    &#64;Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        <strong>setContentView(R.layout.details);</strong>
+    }
+}
+</pre>
+
+<p>Finally, add this new activity to the manifest. Remember to apply the Leanback theme to
+  ensure that the user interface is consistent with the media browse activity:</p>
+
+<pre>
+&lt;application&gt;
+  ...
+
+  &lt;activity android:name=".DetailsActivity"
+    android:exported="true"
+    <strong>android:theme="@style/Theme.Leanback"/&gt;</strong>
+
+&lt;/application&gt;
+</pre>
+
+
+<h3 id="item-listener">Listener for Clicked Items</h3>
+
+<p>After you have implemented the {@code DetailsFragment}, you must modify your main media
+  browsing view to move to your details view when a user clicks on a media item. In order to enable
+  this behavior, add an {@code OnItemClickedListener} object to the BrowseFragment that fires an
+  intent to start the item details activity.</p>
+
+<p>The following example shows how to implement a listener to start the details view when a user
+  clicks a media item in the main media browsing activity:</p>
+
+<pre>
+public class BrowseMediaActivity extends Activity {
+    ...
+
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+
+        // create the media item rows
+        buildRowsAdapter();
+
+        // add a listener for selected items
+        mBrowseFragment.setOnItemClickedListener(
+            new OnItemClickedListener() {
+                &#64;Override
+                public void onItemClicked(Object item, Row row) {
+                    System.out.println("Media Item clicked: " + item.toString());
+                    Intent intent = new Intent(BrowseMediaActivity.this,
+                            DetailsActivity.class);
+                    // pass the item information
+                    intent.getExtras().putLong("id", item.getId());
+                    startActivity(intent);
+                }
+            });
+    }
+}
+</pre>
diff --git a/docs/html/preview/tv/ui/in-app-search.jd b/docs/html/preview/tv/ui/in-app-search.jd
new file mode 100644
index 0000000..3dbfcd2
--- /dev/null
+++ b/docs/html/preview/tv/ui/in-app-search.jd
@@ -0,0 +1,111 @@
+page.title=Adding Search to TV Apps
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#add-search-ui">Add Search User Interface</a></li>
+  </ol>
+
+</div>
+</div>
+
+
+<p>Users frequently have specific content in mind when using a media app. A search interface can
+  help your users get to the content they want faster than browsing. The Leanback library provides a
+  set of classes to enable a standard search interface within your app that is consistent with other
+  search functions on TV and provides features such as voice input.</p>
+
+<h2 id="add-search-ui">Add Search User Interface</h2>
+<p>When you use the BrowseFragment class for your media browsing interface, you can enable the
+  search icon by setting an OnClickListener to the BrowseFragment object. The following sample code
+  demonstrates this technique.</p>
+
+<pre>
+&#64;Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.browse_activity);
+
+    mBrowseFragment = (BrowseFragment)
+            getFragmentManager().findFragmentById(R.id.browse_fragment);
+
+    ...
+
+    mBrowseFragment.setOnSearchClickedListener(new View.OnClickListener() {
+        &#64;Override
+        public void onClick(View view) {
+            Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);
+            startActivity(intent);
+        }
+    });
+
+    mBrowseFragment.setAdapter(buildAdapter());
+}
+</pre>
+
+<p class="note">
+  <strong>Note:</strong> You can set the color of the search icon using the
+  {@code setSearchAffordanceColor()} method of {@code BrowseFragment}.
+</p>
+
+<p>When a user selects the search icon, the system invokes a search activity via the defined
+  Intent. Your search activity should use a linear layout containing a SearchFragment. This fragment
+  must also implement the SearchFragment.SearchResultProvider interface in order to display the
+  results of a search. The following code sample shows how to extend the SearchFragment class to
+  provide a search interface and results:</p>
+
+<pre>
+public class MySearchFragment extends SearchFragment
+        implements SearchFragment.SearchResultProvider {
+
+    private static final int SEARCH_DELAY_MS = 300;
+    private ArrayObjectAdapter mRowsAdapter;
+    private Handler mHandler = new Handler();
+    private SearchRunnable mDelayedLoad;
+
+    &#64;Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        setSearchResultProvider(this);
+        setOnItemClickedListener(getDefaultItemClickedListener());
+        mDelayedLoad = new SearchRunnable();
+    }
+
+    &#64;Override
+    public ObjectAdapter getResultsAdapter() {
+        return mRowsAdapter;
+    }
+
+    &#64;Override
+    public boolean onQueryTextChange(String newQuery) {
+        mRowsAdapter.clear();
+        if (!TextUtils.isEmpty(newQuery)) {
+            mDelayedLoad.setSearchQuery(newQuery);
+            mHandler.removeCallbacks(mDelayedLoad);
+            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+        }
+        return true;
+    }
+
+    &#64;Override
+    public boolean onQueryTextSubmit(String query) {
+        mRowsAdapter.clear();
+        if (!TextUtils.isEmpty(query)) {
+            mDelayedLoad.setSearchQuery(query);
+            mHandler.removeCallbacks(mDelayedLoad);
+            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+        }
+        return true;
+    }
+}
+</pre>
+
+<p>This example code shown above is meant to be used with a separate {@code SearchRunnable}
+  class that runs the search query on a separate thread. This technique keeps potentially
+  slow-running queries from blocking the main user interface thread.</p>
+
diff --git a/docs/html/preview/tv/ui/index.jd b/docs/html/preview/tv/ui/index.jd
new file mode 100644
index 0000000..c861ec2
--- /dev/null
+++ b/docs/html/preview/tv/ui/index.jd
@@ -0,0 +1,40 @@
+page.title=User Interfaces for TV
+
+@jd:body
+
+
+<p>
+  Building an effective and engaging user interface for TV devices requires a firm understanding of what works well
+  in the context of a living room. Imagine a large screen that can be seen by many people at the
+  same time, controlled with a few buttons by users with limited attention, and you start to see the
+  challenges and opportunities of building an app for TV. Building apps for this environment
+  requires a different approach and different tools.</p>
+
+<p>This section discusses how to build a living room experience with your app, including
+  implementation instructions and creating user interface widgets built for TV. Also check out
+  <a href="{@docRoot}design/tv/index.html">Design for TV</a> for information and inspiration
+  on creating engaging user interfaces for TV devices.</p>
+
+<h2>Topics</h2>
+
+<dl>
+  <dt><b><a href="layouts.html">Layouts</a></b></dt>
+    <dd>Learn how to build app layouts for TV screens.</dd>
+
+  <dt><b><a href="navigation.html">Navigation</a></b></dt>
+    <dd>Learn how to build navigation for TV devices.</dd>
+
+  <dt><b><a href="browse.html">BrowseFragment</a></b></dt>
+    <dd>Learn how to use this fragment to build a browsing interface for media catalogs.</dd>
+
+  <dt><b><a href="details.html">DetailsFragment</a></b></dt>
+    <dd>Learn how to use this fragment to build a details page for media items.</dd>
+
+  <dt><b><a href="in-app-search.html">In-App Search</a></b></dt>
+    <dd>Learn how to use a built-for-TV user interface for searching within your app.</dd>
+
+  <dt><b><a href="recommendations.html">Recommendations</a></b></dt>
+    <dd>Learn how your app can contribute to the list of recommendations appearing on the home
+      screen and get your content noticed by users.</dd>
+</dl>
+
diff --git a/docs/html/preview/tv/ui/layouts.jd b/docs/html/preview/tv/ui/layouts.jd
new file mode 100644
index 0000000..0659826
--- /dev/null
+++ b/docs/html/preview/tv/ui/layouts.jd
@@ -0,0 +1,298 @@
+page.title=Layouts for TV
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#themes">Themes</a>
+      <ol>
+        <li><a href="#leanback-theme">Leanback Theme</a></li>
+        <li><a href="#notitle-theme">NoTitleBar Theme</a></li>
+      </ol>
+    </li>
+    <li><a href="#structure">Layout Structure</a>
+      <ol>
+        <li><a href="#overscan">Overscan</a></li>
+      </ol>
+    </li>
+    <li><a href="#visibility">Text and Controls Visibility</a></li>
+    <li><a href="#density-resources">Screen Density and Image Resources</a></li>
+    <li><a href="#anti-patterns">Layout Anti-Patterns</a></li>
+    <li><a href="#large-bitmaps">Handling Large Bitmaps</a></li>
+  </ol>
+
+</div>
+</div>
+
+<p>
+  A TV screen is typically viewed from about 10 feet away, and while it is much larger than most
+  other Android device displays, this type of screen does not provide the same level of precise
+  detail and color as a smaller device. These factors require that you create app layouts with
+  TV devices in mind in order to create a useful and enjoyable user experience.</p>
+
+<p>This guide provides direction and implementation details for building effective layouts inN
+  TV apps.</p>
+
+
+<h2 id="themes">Themes</h2>
+
+<p>Android <a href="{@docRoot}guide/topics/ui/themes.html">Themes</a> can provide a basis for
+  layouts in your TV apps. You should use a theme to modify the display of your app activities
+  that are meant to run on a TV device. This section explains which themes you should use.</p>
+
+
+<h3 id="leanback-theme">Leanback Theme</h3>
+
+<p>The Leanback library provides a standard theme for TV activities, called {@code
+  Leanback.Theme}, which establishes a consistent visual style for TV apps. Use of this theme is
+  recommended for most apps. This theme is recommended for any TV app that uses the Leanback
+  library classes. The following code sample shows how to apply this theme to a given
+  activity within an app:</p>
+
+<pre>
+&lt;activity
+  android:name="com.example.android.TvActivity"
+  android:label="&#64;string/app_name"
+  <strong>android:theme="&#64;style/Theme.Leanback"</strong>&gt;
+</pre>
+
+
+<h3 id="notitle-theme">NoTitleBar Theme</h3>
+
+<p>The title bar is a standard user interface element for Android apps on phones and tablets,
+  but it is not appropriate for TV apps. If you are not using the Leanback library classes,
+  you should apply this theme to your TV activities. The following code example from a TV app
+  manifest demonstrates how to apply this theme to remove the display of a title bar:
+</p>
+
+<pre>
+&lt;application&gt;
+  ...
+
+  &lt;activity
+    android:name="com.example.android.TvActivity"
+    android:label="&#64;string/app_name"
+    <strong>android:theme="&#64;android:style/Theme.NoTitleBar"</strong>&gt;
+    ...
+
+  &lt;/activity&gt;
+&lt;/application&gt;
+</pre>
+
+
+<h2 id="structure">Layout Structure</h2>
+
+<p>Layouts for TV devices should follow some basic guidelines to ensure they are usable and
+  effective on large screens. Follow these tips to build landscape layouts optimized for TV screens:
+</p>
+
+<ul>
+  <li>Build layouts with a landscape orientation. TV screens always display in landscape.</li>
+  <li>Put on-screen navigation controls on the left or right side of the screen and save the
+    vertical space for content.</li>
+  <li>Create UIs that are divided into sections, using <a
+    href="{@docRoot}guide/components/fragments.html"
+  >Fragments</a>, and use view groups like {@link android.widget.GridView} instead of {@link
+    android.widget.ListView} to make better use of the horizontal screen space.
+  </li>
+  <li>Use view groups such as {@link android.widget.RelativeLayout} or {@link
+    android.widget.LinearLayout} to arrange views. This approach allows the system to adjust the
+    position of the views to the size, alignment, aspect ratio, and pixel density of a TV screen.</li>
+  <li>Add sufficient margins between layout controls to avoid a cluttered UI.</li>
+</ul>
+
+
+<h3 id="overscan">Overscan</h3>
+
+<p>Layouts for TV have some unique requirements due to the evolution of TV standards and the
+  desire to always present a full screen picture to viewers. For this reason, TV devices may
+  clip the outside edge of an app layout in order to ensure that the entire display is filled.
+  This behavior is generally referred to as Overscan.</p>
+
+<p>In order to account for the impact of overscan and make sure that all the user interface
+  elements you place in a layout are actually shown on screen, you should incorporate a 10% margin
+  on all sides of your layout. This translates into a 27dp margin on the left and right edges and
+  a 48dp margin on the top and bottom of your base layouts for activities. The following
+  example layout demonstrates how to set these margins in the root layout for a TV app:
+</p>
+
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:id="@+id/base_layout"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical"
+  android:layout_marginTop="27dp"
+  android:layout_marginLeft="48dp"
+  android:layout_marginRight="48dp"
+  android:layout_marginBottom="27dp" &gt;
+&lt;/LinearLayout&gt;
+</pre>
+
+<p class="caution">
+  <strong>Caution:</strong> Do not apply overscan margins to your layout if you are using the
+  Leanback Support Library {@code BrowseFragment} or related widgets, as those layouts already
+  incorporate overscan-safe margins.
+</p>
+
+
+<h2 id="visibility">Text and Controls Visibility</h2>
+
+<p>
+The text and controls in a TV app layout should be easily visible and navigable from a distance.
+Follow these tips to make them easier to see from a distance :
+</p>
+
+<ul>
+  <li>Break text into small chunks that users can quickly scan.</li>
+  <li>Use light text on a dark background. This style is easier to read on a TV.</li>
+  <li>Avoid lightweight fonts or fonts that have both very narrow and very broad strokes.
+  Use simple sans-serif fonts and anti-aliasing to increase readability.</li>
+  <li>Use Android's standard font sizes:
+<pre>
+&lt;TextView
+      android:id="@+id/atext"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:gravity="center_vertical"
+      android:singleLine="true"
+      android:textAppearance="?android:attr/textAppearanceMedium"/&gt;
+</pre>
+  </li>
+  <li>Ensure that all your view widgets are large enough to be clearly visible to someone
+    sitting 10 feet away from the screen (this distance is greater for very large screens). The
+    best way to do this is to use layout-relative sizing rather than absolute sizing, and
+    density-independent pixel units instead of absolute pixel units. For example, to set the
+    width of a widget, use wrap_content instead of a pixel measurement, and to set the margin
+    for a widget, use dip instead of px values.</li>
+</ul>
+
+
+<h2 id="density-resources">Screen Density and Image Resources</h2>
+
+<p>The common high-definition TV display resolutions are 720p, 1080i, and 1080p.
+  Your TV layout should target a screen size of 1920 x 1080 pixels, and then allow the Android
+  system to downscale your layout elements to 720p if necessary. In general, downscaling
+  (removing pixels) does not degrade your layout presentation quality. However, upscaling can
+  cause display artifacts that degrade the quality of your layout and have a negative impact on
+  the user experience of your app.</p>
+
+<p>
+  To get the best scaling results for images, provide them as
+  <a href="{@docRoot}tools/help/draw9patch.html">9-patch image</a> elements if possible. If you
+  provide low quality or small images in your layouts, they will appear pixelated, fuzzy, or
+  grainy. This is not a good experience for the user. Instead, use high-quality images.
+</p>
+
+<p>
+  For more information on optimizing layouts and resources for large screens see
+  <a href="{@docRoot}training/multiscreen/index.html">Designing for multiple screens</a>.
+</p>
+
+
+<h2 id="anti-patterns">Layout Anti-Patterns</h2>
+
+<p>There are a few approaches to building layouts for TV that you should avoid because they do not
+work well and lead to bad user experiences. Here are some user interface approaches you
+should specifically <em>not</em> use when developing a layout for TV.
+</p>
+
+<ul>
+  <li><strong>Re-using phone or tablet layouts</strong> - Do not reuse layouts from a phone or
+    tablet app without modification. Layouts built for other Android device form factors are not
+    well suited for TV devices and should be simplified for operation on a TV.</li>
+  <li><strong>ActionBar</strong> - While this user interface convention is recommended for use
+    on phones and tablets, it is not appropriate for a TV interface. In particular, using an
+    action bar options menu (or any pull-down menu for that matter) is strongly discouraged, due
+    to the difficulty in navigating such a menu with a remote control.</li>
+  <li><strong>ViewPager</strong> - Sliding between screens can work great on a phone or tablet,
+    but don't try this on a TV!</li>
+
+</ul>
+
+<p>For more information on designing layouts that are appropriate to TV, see the
+  <a href="{@docRoot}design/tv/index.html">TV Design</a> guide.</p>
+
+
+<h2 id="large-bitmaps">Handling Large Bitmaps</h2>
+
+<p>TV devices, like any other Android device, have a limited amount of memory. If you build your
+  app layout with very high-resolution images or use many high-resolution images in the operation
+  of your app, it can quickly run into memory limits and cause out of memory errors.
+  To avoid these types of problems, follow these tips:</p>
+
+<ul>
+  <li>Load images only when they're displayed on the screen. For example, when displaying multiple images in
+      a {@link android.widget.GridView} or
+      {@link android.widget.Gallery}, only load an image when
+      {@link android.widget.Adapter#getView(int, View, ViewGroup) getView()}
+      is called on the View's {@link android.widget.Adapter}.
+  </li>
+  <li>Call {@link android.graphics.Bitmap#recycle()} on
+      {@link android.graphics.Bitmap} views that are no longer needed.
+  </li>
+  <li>Use {@link java.lang.ref.WeakReference} for storing references
+      to {@link android.graphics.Bitmap} objects in an in-memory
+      {@link java.util.Collection}.</li>
+  <li>If you fetch images from the network, use {@link android.os.AsyncTask}
+      to fetch and store them on the device for faster access.
+      Never do network transactions on the application's UI thread.
+  </li>
+  <li>Scale down large images to a more appropriate size as you download them;
+    otherwise, downloading the image itself may cause an out of memory exception.
+    The following sample code demonstrates how to scale down images while downloading:
+<pre>
+  // Get the source image's dimensions
+  BitmapFactory.Options options = new BitmapFactory.Options();
+  // This does not download the actual image, just downloads headers.
+  options.inJustDecodeBounds = true;
+  BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+  // The actual width of the image.
+  int srcWidth = options.outWidth;
+  // The actual height of the image.
+  int srcHeight = options.outHeight;
+
+  // Only scale if the source is bigger than the width of the destination view.
+  if(desiredWidth > srcWidth)
+    desiredWidth = srcWidth;
+
+  // Calculate the correct inSampleSize/scale value. This approach helps reduce
+  // memory use. This value should be a power of 2.
+  int inSampleSize = 1;
+  while(srcWidth / 2 > desiredWidth){
+    srcWidth /= 2;
+    srcHeight /= 2;
+    inSampleSize *= 2;
+  }
+
+  float desiredScale = (float) desiredWidth / srcWidth;
+
+  // Decode with inSampleSize
+  options.inJustDecodeBounds = false;
+  options.inDither = false;
+  options.inSampleSize = inSampleSize;
+  options.inScaled = false;
+  // Ensures the image stays as a 32-bit ARGB_8888 image.
+  // This preserves image quality.
+  options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+  Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+
+  // Resize
+  Matrix matrix = new Matrix();
+  matrix.postScale(desiredScale, desiredScale);
+  Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0,
+      sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true);
+  sampledSrcBitmap = null;
+
+  // Save
+  FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE);
+  scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
+  scaledBitmap = null;
+</pre>
+  </li>
+</ul>
+
diff --git a/docs/html/preview/tv/ui/navigation.jd b/docs/html/preview/tv/ui/navigation.jd
new file mode 100644
index 0000000..92b34cf
--- /dev/null
+++ b/docs/html/preview/tv/ui/navigation.jd
@@ -0,0 +1,136 @@
+page.title=Navigation for TV
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#d-pad-navigation">D-pad Navigation</a></li>
+    <li><a href="#focus-selection">Focus and Selection</a></li>
+  </ol>
+
+</div>
+</div>
+
+<p>TV devices provide a limited set of navigation controls for apps. Creating an effective
+  navigation scheme for your TV app depends on understanding these limited controls and the limits
+  of users' perception while operating your app. As you build your Android app for TVs,
+  you should pay special attention to how the user actually navigates around your app
+  when using remote control buttons instead of a touch screen.</p>
+
+<p>This guide shows you how to build an effective navigation scheme for your TV app.</p>
+
+
+<h2 id="d-pad-navigation">D-pad Navigation</h2>
+
+<p>On a TV device, users navigate with controls on a remote control device, using either a
+  directional pad (D-pad) or arrow keys. This type of control limits movement to up, down, left,
+  and right. To build a great TV-optimized app, you must provide a navigation scheme where
+  the user can quickly learn how to navigate your app using these limited controls.</p>
+
+<p>Follow these guidelines to build a navigation system that works well with a D-pad on a TV device:
+</p>
+
+<ul>
+  <li>Ensure that the D-pad can navigate to all the visible controls on the screen.</li>
+  <li>For scrolling lists with focus, D-pad up/down keys scroll the list, and the Enter key selects
+    an item in the list. Ensure that users can select an element in the list and that the list still
+    scrolls when an element is selected.</li>
+  <li>Ensure that movement between controls is straightforward and predictable.</li>
+</ul>
+
+<p>The Android framework handles directional navigation between layout elements automatically, so
+  you typically do not need to do anything extra for your app. However, you should thoroughly test
+  navigation with a D-pad control to discover any navigation problems. If you discover that your
+  screen layout makes navigation difficult, or if you want users to move through the layout in a
+  specific way, you can set up explicit directional navigation for your controls. The following
+  code sample shows how to define the next control to receive focus for a
+  {@link android.widget.TextView} layout object:</p>
+
+<pre>
+&lt;TextView android:id="&#64;+id/Category1"
+             android:nextFocusDown="&#64;+id/Category2"\&gt;
+</pre>
+
+<p>The following table lists all of the available navigation attributes for Android user interface
+widgets:</p>
+
+<table>
+  <tr>
+    <th>Attribute</th>
+    <th>Function</th>
+  </tr>
+  <tr>
+    <td>{@link android.R.attr#nextFocusDown}</td>
+    <td>Defines the next view to receive focus when the user navigates down.</td>
+  </tr>
+  <tr>
+    <td>{@link android.R.attr#nextFocusLeft}</td>
+    <td>Defines the next view to receive focus when the user navigates left.</td>
+  </tr>
+  <tr>
+    <td>{@link android.R.attr#nextFocusRight}</td>
+    <td>Defines the next view to receive focus when the user navigates right.</td>
+  </tr>
+  <tr>
+    <td>{@link android.R.attr#nextFocusUp}</td>
+    <td>Defines the next view to receive focus when the user navigates up.</td>
+  </tr>
+</table>
+
+<p>To use one of these explicit navigation attributes, set the value to the ID ({@code android:id}
+  value) of another widget in the layout. You should set up the navigation order as a loop, so that
+  the last control directs focus back to the first one.</p>
+
+<p class="note">
+  <strong>Note:</strong> You should only use these attributes to modify the navigation order if the
+  default order that the system applies does not work well.
+</p>
+
+
+<h2 id="focus-selection">Focus and Selection</h2>
+
+<p>The success of a navigation scheme on TV devices is strongly dependent on how easy it is for a
+  user to determine what user interface element is in focus on screen. If you do not provide clear
+  indications of what is in focus on screen (and therefore what item they can take action on),
+  users can quickly become frustrated and exit your app. By the same token, it is important
+  to always have an item in focus that a user can take action on immediately after your app starts,
+  and any time your app is not playing content.</p>
+
+<p>Your app layout and implementation should use color, size, animation, or a combination of
+  these attributes to help users easily determine what actions they can take next. Use a uniform
+  scheme for indicating focus across your application.</p>
+
+<p>Android provides <a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">
+Drawable State List Resources</a> to implement highlights for selected and focused controls. The
+following code example demonstates how to indicate selection of a button object:
+</p>
+
+<pre>
+&lt;!-- res/drawable/button.xml --&gt;
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+    &lt;item android:state_pressed="true"
+          android:drawable="@drawable/button_pressed" /&gt; &lt;!-- pressed --&gt;
+    &lt;item android:state_focused="true"
+          android:drawable="@drawable/button_focused" /&gt; &lt;!-- focused --&gt;
+    &lt;item android:state_hovered="true"
+          android:drawable="@drawable/button_focused" /&gt; &lt;!-- hovered --&gt;
+    &lt;item android:drawable="@drawable/button_normal" /&gt; &lt;!-- default --&gt;
+&lt;/selector&gt;
+</pre>
+
+<p>
+This layout XML applies the above state list drawable to a {@link android.widget.Button}:
+</p>
+<pre>
+&lt;Button
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:background="@drawable/button" /&gt;
+</pre>
+
+<p>Make sure to provide sufficient padding within the focusable and selectable controls so that
+  the highlights around them are clearly visible.</p>
+
diff --git a/docs/html/preview/tv/ui/recommendations.jd b/docs/html/preview/tv/ui/recommendations.jd
new file mode 100644
index 0000000..2c78064
--- /dev/null
+++ b/docs/html/preview/tv/ui/recommendations.jd
@@ -0,0 +1,209 @@
+page.title=Making Recommendations
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#service">Create a Recommendations Service</a></li>
+    <li><a href="#build">Build Recommendations</a></li>
+    <li><a href="#run-service">Run Recommendations Service</a></li>
+    <li><a href="#DesignLandscapeLayouts">Design Landscape Layouts</a></li>
+  </ol>
+
+</div>
+</div>
+
+
+<p>Content recommendations appear as the first row of the TV launch screen after the first use
+  of the device. This row is intended to help users quickly find content they enjoy. Contributing
+  recommendations from your apps content catalog can help bring users back to your app.</p>
+
+
+<img src="{@docRoot}preview/tv/images/home-recommendations.png" alt="" id="figure1" />
+<p class="img-caption">
+  <strong>Figure 1.</strong> An example of the recommendations row.
+</p>
+
+
+<h2 id="service">Create a Recommendations Service</h2>
+
+<p>Content recommendations are created with background processing. In order for your application
+  to contribute to recommendations, you create a service that periodically adds listings from your
+  app's catalog to the system list of recommendations.</p>
+
+<p>The following code example illustrates how to extend the {@link android.app.IntentService} to
+  create a recommendation service for your application.</p>
+
+<pre>
+public class RecommendationsService extends IntentService {
+    private static final int MAX_RECOMMENDATIONS = 3;
+
+    public RecommendationsService() {
+        super("RecommendationService");
+    }
+
+    &#64;Override
+    protected void onHandleIntent(Intent intent) {
+        MovieDatabase database = MovieDatabase.instance(getApplicationContext());
+        List<Movie> recommendations = database.recommendations();
+
+        int count = 0;
+
+        try {
+            for (Movie movie : recommendations) {
+                // build the individual content recommendations
+                buildRecommendation(getApplicationContext(), movie);
+
+                if (++count >= MAX_RECOMMENDATIONS) {
+                    break;
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to update recommendation", e);
+        }
+    }
+}
+</pre>
+
+<p>In order for this class to be recognized and run as a service, you must register this service
+  using your app manifest. The following code snippet illustrates how to add this class as a
+  service:</p>
+
+<pre>
+&lt;manifest ... &gt;
+  &lt;application ... &gt;
+    ...
+
+    &lt;service android:name=&quot;.RecommendationsService&quot;
+             android:enabled=&quot;true&quot; android:exported=&quot;true&quot;/&gt;
+  &lt;/application&gt;
+&lt;/manifest&gt;
+</pre>
+
+<h2 id="build">Build Recommendations</h2>
+
+<p>Once it starts running, your service must create recommendations and pass them to the Android
+  framework. The framework receives the recommendations as {@link android.app.Notification} objects
+  that use a specific style and are marked with a specific category.</p>
+
+<p>The following code example demonstrates how to get an instance of the {@link
+  android.app.NotificationManager}, build a recommendation, and post it to the manager:</p>
+
+<pre>
+public class RecommendationsService extends IntentService {
+
+    ...
+
+    public Notification buildRecommendation(Context context, Movie movie)
+            throws IOException {
+
+        if (mNotificationManager == null) {
+            mNotificationManager = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        Bundle extras = new Bundle();
+        if (mBackgroundUri != movie.getBackgroundUri()) {
+            extras.putString(EXTRA_BACKGROUND_IMAGE_URL, movie.getBackgroundUri());
+        }
+
+        // build the recommendation as a Notification object
+        Notification notification = new NotificationCompat.BigPictureStyle(
+                new NotificationCompat.Builder(context)
+                        .setContentTitle(movie.getTitle())
+                        .setContentText(movie.getDescription())
+                        .setPriority(movie.getPriority())
+                        .setOngoing(true)
+                        .setCategory("recommendation")
+                        .setLargeIcon(movie.getImage())
+                        .setSmallIcon(movie.getSmallIcon())
+                        .setContentIntent(buildPendingIntent(movie.getId()))
+                        .setExtras(extras))
+                .build();
+
+        // post the recommendation to the NotificationManager
+        mNotificationManager.notify(movie.getId(), notification);
+        mNotificationManager = null;
+        return notification;
+    }
+
+    private PendingIntent buildPendingIntent(long id) {
+        Intent detailsIntent = new Intent(this, DetailsActivity.class);
+        detailsIntent.putExtra("id", id);
+
+        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+        stackBuilder.addParentStack(DetailsActivity.class);
+        stackBuilder.addNextIntent(detailsIntent);
+        // Ensure each PendingIntent is unique
+        detailsIntent.setAction(Long.toString(id));
+
+        PendingIntent intent = stackBuilder.getPendingIntent(
+                0, PendingIntent.FLAG_UPDATE_CURRENT);
+        return intent;
+    }
+}
+</pre>
+
+
+<h3 id="run-service">Run Recommendations Service</h3>
+
+<p>Your app's recommendation service must run periodically in order to create current
+  recommendations. In order to run your service, you should create a class that runs a timer and
+  invokes it at regular intervals. The following code example extends the {@link
+  android.content.BroadcastReceiver} class to start periodic execution of a recommendation service
+  every 12 hours:</p>
+
+<pre>
+public class BootupReceiver extends BroadcastReceiver {
+    private static final String TAG = "BootupActivity";
+
+    private static final long INITIAL_DELAY = 5000;
+
+    &#64;Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
+            scheduleRecommendationUpdate(context);
+        }
+    }
+
+    private void scheduleRecommendationUpdate(Context context) {
+        AlarmManager alarmManager = (AlarmManager)context.getSystemService(
+                Context.ALARM_SERVICE);
+        Intent recommendationIntent = new Intent(context,
+                UpdateRecommendationsService.class);
+        PendingIntent alarmIntent = PendingIntent.getService(context, 0,
+                recommendationIntent, 0);
+
+        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                INITIAL_DELAY,
+                AlarmManager.INTERVAL_HALF_DAY,
+                alarmIntent);
+    }
+}
+</pre>
+
+<p>In order for the {@link android.content.BroadcastReceiver} class to execute after a TV
+  device starts up, you must register this class in your app manifest and attach an intent filter
+  in order for the device boot process to complete. This sample code demonstrates how to add this
+  configuration to the manifest:</p>
+
+<pre>
+&lt;manifest ... &gt;
+  &lt;application ... &gt;
+    &lt;receiver android:name=&quot;.BootupReceiver&quot; android:enabled=&quot;true&quot;
+              android:exported=&quot;false&quot;&gt;
+      &lt;intent-filter&gt;
+        &lt;action android:name=&quot;android.intent.action.BOOT_COMPLETED&quot;/&gt;
+      &lt;/intent-filter&gt;
+    &lt;/receiver&gt;
+  &lt;/application&gt;
+&lt;/manifest&gt;
+</pre>
+
+<p class="important">
+  <strong>Important:</strong> Receiving a boot completed notification requires that your app
+  request the {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission.
+  For more information, see {@link android.content.Intent#ACTION_BOOT_COMPLETED}.
+</p>
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..14a5e1d 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -4,45 +4,45 @@
 header.hide=1
 page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices.
 
-sdk.version=22.6.2
+sdk.version=23.0.0
 
-sdk.linux32_bundle_download=adt-bundle-linux-x86-20140321.zip
-sdk.linux32_bundle_bytes=527971926
-sdk.linux32_bundle_checksum=943ae4d28fe7c79108c8bf2aafd5e6d2
+sdk.linux32_bundle_download=adt-bundle-linux-x86-20140624.zip
+sdk.linux32_bundle_bytes=378659422
+sdk.linux32_bundle_checksum=692e6135ed459f1e8a10498363f19f67
 
-sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20140321.zip
-sdk.linux64_bundle_bytes=528187678
-sdk.linux64_bundle_checksum=f2a2153b5c7dbaeb86b550bf4f770c36
+sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20140624.zip
+sdk.linux64_bundle_bytes=378966059
+sdk.linux64_bundle_checksum=0f14b4aed1eb1feed778ad6ed76ba01c
 
-sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20140321.zip
-sdk.mac64_bundle_bytes=501955296
-sdk.mac64_bundle_checksum=4a08649cea9b098cdf7349f452294014
+sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20140624.zip
+sdk.mac64_bundle_bytes=327367424
+sdk.mac64_bundle_checksum=7d16e83263259882901f12055a8fe3f2
 
-sdk.win32_bundle_download=adt-bundle-windows-x86-20140321.zip
-sdk.win32_bundle_bytes=535085536
-sdk.win32_bundle_checksum=b61495a6bf591cc374c31bce4fc46ec0
+sdk.win32_bundle_download=adt-bundle-windows-x86-20140624.zip
+sdk.win32_bundle_bytes=377325518
+sdk.win32_bundle_checksum=5655cd8be53c4b27c5242d81943c5a25
 
-sdk.win64_bundle_download=adt-bundle-windows-x86_64-20140321.zip
-sdk.win64_bundle_bytes=535287324
-sdk.win64_bundle_checksum=a6f4699bbdc5a29b371ed60610535651
+sdk.win64_bundle_download=adt-bundle-windows-x86_64-20140624.zip
+sdk.win64_bundle_bytes=377477237
+sdk.win64_bundle_checksum=0f1fa29a0f229e36ba0fb87bb7ee68d4
 
 
 
-sdk.linux_download=android-sdk_r22.6.2-linux.tgz
-sdk.linux_bytes=101050024
-sdk.linux_checksum=ff1541418a44d894bedc5cef10622220
+sdk.linux_download=android-sdk_r23-linux.tgz
+sdk.linux_bytes=137880976
+sdk.linux_checksum=fd768c56423e398b3e4aa8895c393bf5
 
-sdk.mac_download=android-sdk_r22.6.2-macosx.zip
-sdk.mac_bytes=74639394
-sdk.mac_checksum=2a319c862dd1dcf450bfe2a6b3d9c608
+sdk.mac_download=android-sdk_r23-macosx.zip
+sdk.mac_bytes=88015023
+sdk.mac_checksum=3869e5b9de8d69f90509568686fb7ce8
 
-sdk.win_download=android-sdk_r22.6.2-windows.zip
-sdk.win_bytes=108917505
-sdk.win_checksum=6e5bfdb7b9c1d231ed6ec78b31551cbf
+sdk.win_download=android-sdk_r23-windows.zip
+sdk.win_bytes=138459944
+sdk.win_checksum=9daba72b3a15a6154fe6ca1ada817553
 
-sdk.win_installer=installer_r22.6.2-windows.exe
-sdk.win_installer_bytes=87383126
-sdk.win_installer_checksum=2a68b8b22ecd0aba779b1581a914b395
+sdk.win_installer=installer_r23-windows.exe
+sdk.win_installer_bytes=90065639
+sdk.win_installer_checksum=4564d1f1b30c001c78a22eec40444e5f
 
 
 
@@ -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 -->
 
@@ -232,7 +230,6 @@
 </div>
 
 
-
 </div><!-- end TOS -->
 
 
@@ -255,16 +252,30 @@
 <b>ADT (Android Developer Tools)</b> to
 streamline your Android app development.</p>
 
-<p>With a single download, the ADT Bundle
+
+<!-- this appears when viewing the online docs -->
+<div class="online" style="margin-bottom:85px">
+
+<a class="big button subtitle" id="download-bundle-button"
+href="" style="width:295px;display:block;margin:25px 0" ></a>
+
+<p id="not-supported">Choose the SDK package for your OS from the table below.</p>
+
+
+  <p>With a single download, the Eclipse ADT bundle
 includes everything you need to begin developing apps:</p>
 <ul>
 <li>Eclipse + ADT plugin</li>
 <li>Android SDK Tools</li>
 <li>Android Platform-tools</li>
-<li>The latest Android platform</li>
-<li>The latest Android system image for the emulator</li>
+<li>A version of the Android platform</li>
+<li>A version of the Android system image for the emulator</li>
 </ul>
 
+</div>
+<!-- end online -->
+
+
 
 <!-- this appears when viewing the offline docs -->
 <p class="offline">
@@ -277,52 +288,44 @@
 
 
 <div class="col-7" style="margin-right:0;">
-  <img src="{@docRoot}images/sdk-cube.png" alt="" height=264 />
+  <img src="{@docRoot}images/tools-home.png" alt="" height="347" width="400" />
 
-<!-- this appears when viewing the online docs -->
-<div class="online">
-
-<a class="big button subtitle" id="download-bundle-button"
-href="" style="display:none;width:265px;margin:0 auto;display:block" ></a>
-
-
-
-
-<p id="not-supported">Choose the SDK package for your OS from the table below.</p>
-
-</div>
-<!-- end online -->
 
 </div><!-- end col-7 -->
 
 
 
 
+<div class="col-7" style="background: #ddd;
+    padding: 30px 20px; width:350px; margin:20px 0 0 20px;">
+
+  <h3 style="margin-top:0">
+  <a href="/sdk/installing/studio.html">Get Android Studio Beta</a>
+  </h3>
+
+  <p>
+  Android Studio is a new IDE powered by IntelliJ that provides new features and improvements
+  over ADT. It's currently in beta but will be the official Android IDE once it's ready.</p>
+  <p style="margin: 0;">
+  <a href="/sdk/installing/studio.html">Learn more about Android Studio</a></p>
+  </div>
+
+
 
 
 <!-- alternative SDK options -->
-<div class="col-13" style="margin:0;">
+<div class="col-13" style="margin:-70px 0 0;">
 
 
-<!-- this appears only when viewing the online docs -->
-<div class="online caution">
-<h3 style="margin:0 0 10px 0;font-size:14px">Android Studio Early Access Preview</h3>
+<p style="width:340px">If you prefer to use an existing version of Eclipse or another IDE,
+you can instead download the stand-alone Android SDK Tools:</p>
 
-<p>A new Android development environment called Android Studio,
-based on IntelliJ IDEA, is now available as an <strong>early access preview</strong>.
-For more information, see
-<a href="{@docRoot}sdk/installing/studio.html">Getting Started with Android Studio</a>.</p>
 
-</div>
-
-<p>If you prefer to use an existing version of Eclipse or another IDE,
-you can instead take a more customized approach to installing
-the Android SDK. See the following instructions:</p>
 
 
 <h4 id="ExistingIDE"><a href='' class="expandable"
   onclick="toggleExpandable(this,'.myide');hideExpandable('.pax,.reqs');return false;"
-  >USE AN EXISTING IDE</a></h4>
+  >GET THE SDK FOR AN EXISTING IDE</a></h4>
 
 <div class="col-13 myide" style="margin:0 0 15px;display:none;">
 
@@ -335,13 +338,11 @@
 <a class="button subtitle" id="download-tools-button" href="" style="display:none" ></a>
   </p>
 
-
 </div>
 
 
 
 
-
 <h4 id="Requirements"><a href='' class="expandable"
   onclick="toggleExpandable(this,'.reqs');hideExpandable('.pax,.myide');return false;"
   >SYSTEM REQUIREMENTS</a></h4>
@@ -361,23 +362,9 @@
 </ul>
 </div>
 
-<div class="col-6 reqs" style="margin:0 0 15px 20px;display:none;">
-<h5>Eclipse IDE</h5>
-    <ul>
-      <li><a href="http://eclipse.org/mobile/">Eclipse</a> 3.7.2 (Indigo) or greater
-<p class="note"><strong>Note:</strong> Eclipse 3.6 (Helios) is no longer
-supported with the latest version of ADT.</p></li>
-      <li>Eclipse <a href="http://www.eclipse.org/jdt">JDT</a> plugin (included
-in most Eclipse IDE packages) </li>
-      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 6</a>
-        (JRE alone is not sufficient)</li>
-      <li><a href="{@docRoot}tools/sdk/eclipse-adt.html">Android Development Tools plugin</a>
-(recommended)</li>
-      <li><strong>Not</strong> compatible with GNU Compiler for Java (gcj)</li>
-    </ul>
+<div class="col-7 reqs" style="margin:0 0 80px 20px;display:none;">
 
-
-<h5>Other development environments</h5>
+<h5>Development tools</h5>
     <ul>
       <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 6</a>
         (JRE alone is not sufficient)</li>
@@ -387,6 +374,4 @@
 
 <p class="note"><strong>Note:</strong> Some Linux distributions may include JDK 1.4 or Gnu Compiler
 for Java, both of which are <em>not</em> supported for Android development. </p>
-</div><!-- end col-7 reqs -->
-
-
+</div><!-- end reqs -->
diff --git a/docs/html/sdk/installing/adding-packages.jd b/docs/html/sdk/installing/adding-packages.jd
index bba936e..e6c0118 100644
--- a/docs/html/sdk/installing/adding-packages.jd
+++ b/docs/html/sdk/installing/adding-packages.jd
@@ -1,63 +1,226 @@
-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>
+
+<p>When you open the SDK Manager for the first time, several packages will be selected by
+default. Leave these selected, but be sure you have everything you need
+to get started by following these steps:</p>
+
+
+<ol class="large">
+<li>
+  <h2 id="GetTools" 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> (highest version)</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>
+  </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 id="GetSupportLib" 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">
+    <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>It 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>Open the <strong>Extras</strong> directory and select:</p>
+     <ul>
+       <li><strong>Android Support Repository</strong></li>
+       <li><strong>Android Support Library</strong></li>
+     </ul>
+
+  <p>&nbsp;</p>
+  <p>&nbsp;</p>
+
+</li>
+
+
+<li>
+  <h2 id="GetGoogle" class="norule">Get Google Play services for even more APIs</h2>
+
+  <div class="sidebox">
+
+    <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>
+  <p>Open the <strong>Extras</strong> directory and select:</p>
+     <ul>
+       <li><strong>Google Repository</strong></li>
+       <li><strong>Google Play services</strong></li>
+     </ul>
+
+  <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 id="Install" class="norule">Install the packages</h2>
+  <p>Once you've selected all the desired packages, continue to install:</p>
+  <ol>
+   <li>Click <strong>Install X packages</strong>.</li>
+   <li>In the next window, double-click each package name on the left
+   to accept the license agreement for each.</li>
+   <li>Click <strong>Install</strong>.</li>
+  </ol>
+  <p>The download progress is shown at the bottom of the SDK Manager window.
+  <strong>Do not exit the SDK Manager</strong> or it will cancel the download.</p>
+</li>
+
+<li>
+  <h2 id="Build" 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/play-services/setup.html">Setting Up Google Play
+Services</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/create-project.jd b/docs/html/sdk/installing/create-project.jd
new file mode 100644
index 0000000..c0d523a
--- /dev/null
+++ b/docs/html/sdk/installing/create-project.jd
@@ -0,0 +1,86 @@
+page.title=Creating a Project
+
+@jd:body
+
+<p>Android Studio makes it easy to create Android apps for several form factors, such as phone,
+tablet, TV, Wear, and Google Glass. The <em>New Project</em> wizard lets you choose the form factors
+for your app and populates the project structure with everything you need to get started.</p>
+
+<p>Follow the steps in this section to create a project in Android Studio.</p>
+
+<h2>Step 1: Create a New Project</h2>
+
+<p>If you didn't have a project opened, Android Studio shows the Welcome screen.
+To create a new project, click <strong>New Project</strong>.</p>
+
+<p>If you had a project opened, Android Studio shows the development environment.
+To create a new project, click <strong>File</strong> > <strong>New Project</strong>.</p>
+
+<p>The next window lets you configure the name of your app, the package name, and the location
+of your project.</p>
+
+<img src="{@docRoot}images/tools/wizard2.png" alt="" width="500" height="381">
+<p class="img-caption"><strong>Figure 1.</strong> Choose a name for your project.</p>
+
+<p>Enter the values for your project and click <strong>Next</strong>.</p>
+
+
+<h2>Step 2: Select Form Factors and API Level</h2>
+
+<p>The next window lets you select the form factors supported by your app, such as phone, tablet,
+TV, Wear, and Google Glass. For each form factor, you can also select the API
+Level that your app requires. To get more information, click <strong>Help me choose</strong>.</p>
+
+<img src="{@docRoot}images/tools/wizard4.png" alt="" width="750" height="510">
+<p class="img-caption"><strong>Figure 2.</strong> Select the API Level.</p>
+
+<p>The API Level window shows the distribution of mobile devices running each version of Android,
+as shown in Figure 2. Click on an API level to see a list of features introduced in the corresponding
+version of Android. This helps you choose the minimum API Level that has all the features that
+your apps needs, so you can reach as many devices as possible. Then click <strong>OK</strong>.</p>
+
+<img src="{@docRoot}images/tools/wizard3.png" alt="" width="500" height="480">
+<p class="img-caption"><strong>Figure 3.</strong> Choose form factors for your app.</p>
+
+<p>Then, on the Form Factors Window, click <strong>Next</strong>.</p>
+
+
+<h2>Step 3: Add an Activity</h2>
+
+<p>The next screen lets you select an activity type to add to your app, as shown in Figure 4.
+This screen depends on the form factors you selected earlier.</p>
+
+<img src="{@docRoot}images/tools/wizard5.png" alt="" width="720" height="504">
+<p class="img-caption"><strong>Figure 4.</strong> Add an activity to your app.</p>
+
+<p>Choose an activity type and click <strong>Next</strong>.</p>
+
+
+<h2>Step 4: Configure Your Activity</h2>
+
+<p>The next screen lets you configure the activity to add to your app, as shown in Figure 6.</p>
+
+<img src="{@docRoot}images/tools/wizard6.png" alt="" width="450" height="385">
+<p class="img-caption"><strong>Figure 6.</strong> Choose a name for your activity.</p>
+
+<p>Enter the activity name, the layout name, and the activity title. Then click
+<strong>Finish</strong>.</p>
+
+
+<h2>Step 5: Develop Your App</h2>
+
+<p>Android Studio creates the default structure for your project and opens the development
+environment. If your app supports more than one form factor, Android Studio creates a module for
+each of them, as shown in Figure 7.</p>
+
+<img src="{@docRoot}images/tools/wizard7.png" alt="" width="750" height="509">
+<p class="img-caption"><strong>Figure 7.</strong> The default project structure for a mobile app.</p>
+
+<p>Now you are ready to develop your app. Fore more information, see the following links:</p>
+
+<ul>
+<li><a href="{@docRoot}training/">Training Lessons</a></li>
+<li><a href="{@docRoot}training/building-wearables.html">Building Apps for Wearables</a></li>
+<li><a href="{@docRoot}tv/">Android TV</a></li>
+<li><a href="https://developers.google.com/glass/">Google Glass</a></li>
+</ul>
diff --git a/docs/html/sdk/installing/index.jd b/docs/html/sdk/installing/index.jd
index 6b63ba7..304b53d 100644
--- a/docs/html/sdk/installing/index.jd
+++ b/docs/html/sdk/installing/index.jd
@@ -1,19 +1,217 @@
-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>
+p.paging-links {
+  margin:0 0 40px;
+}
+.paging-links .next-page-link {
+  right:initial;
+}
+.procedure-box {
+  padding:20px 20px 5px;
+  margin-bottom:1em;
+  background:#eee;
+}
+.landing-button {
+  min-width: 155px;
+  text-align: center;
+}
+</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.
+If you need 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.
+If you need 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 +219,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 +229,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 +300,122 @@
 </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    ##################### -->
+
+
+<div id="default" style="display:none">
+
+<p>If you haven't already, <b><a href="{@docRoot}sdk/index.html">download
+the Android SDK</a></b>. </p>
+
+<p>Then, select which SDK package you want to install:</p>
+
+<div class="cols" style="margin:30px 0 60px">
+<div class="col-4" style="margin-left:0">
+<a href="{@docRoot}sdk/installing/index.html?pkg=adt" class="landing-button landing-secondary">
+Eclipse ADT
+</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" class="landing-button landing-secondary">
+Android Studio
+</a>
+</div>
+
+<div class="col-4">
+<a href="{@docRoot}sdk/installing/index.html?pkg=tools" class="landing-button landing-secondary">
+Stand-alone SDK Tools
+</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..b13983b 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -1,8 +1,8 @@
 page.title=Installing the Eclipse Plugin
-adt.zip.version=22.6.3
-adt.zip.download=ADT-22.6.3.zip
-adt.zip.bytes=14590813
-adt.zip.checksum=3982259fd2cc81e53bbbe05dcd6529a7
+adt.zip.version=23.0.0
+adt.zip.download=ADT-23.0.0.zip
+adt.zip.bytes=104001830
+adt.zip.checksum=a1bc9b15b69b43bda358ea09a2feb5f7
 
 @jd:body
 
@@ -15,19 +15,28 @@
 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> 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>
 
-
-<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>Your existing Eclipse installation must meet these requirements:</p>
+    <ul>
+      <li><a href="http://eclipse.org/mobile/">Eclipse</a> 3.7.2 (Indigo) or greater
+<p class="note"><strong>Note:</strong> Eclipse 3.6 (Helios) is no longer
+supported with the latest version of ADT.</p></li>
+      <li>Eclipse <a href="http://www.eclipse.org/jdt">JDT</a> plugin (included
+in most Eclipse IDE packages) </li>
+      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 6</a>
+        (JRE alone is not sufficient)</li>oid Development Tools plugin</a>
+(recommended)</li>
+      <li><strong>Not</strong> compatible with GNU Compiler for Java (gcj)</li>
+    </ul>
 
 
 <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>
@@ -75,7 +84,7 @@
 <div class="sidebox-wrapper">
 <div class="sidebox">
 <h2>App Translations in Google Play</h2>
-<p>Google Play <a href="{@docRoot}distribute/tools/localization-checklist.html#gp-trans">App 
+<p>Google Play <a href="{@docRoot}distribute/tools/localization-checklist.html#gp-trans">App
 Translation Service</a> is available in the Developer Console to help you
 localize your app for a global user base. You can browse qualified vendors, get
 estimates, upload strings for translation, and then import the translations directly
diff --git a/docs/html/sdk/installing/studio.jd b/docs/html/sdk/installing/studio.jd
index a2c32f0..3b7a7da 100644
--- a/docs/html/sdk/installing/studio.jd
+++ b/docs/html/sdk/installing/studio.jd
@@ -1,5 +1,7 @@
-page.title=Getting Started with Android Studio
-page.tags=studio
+page.title=Android Studio
+page.tags="studio"
+page.image=images/resource-card-android-studio.png
+page.metaDescription=Learn about the new features in the beta release of our new IDE.
 @jd:body
 
 
@@ -7,7 +9,7 @@
 
 <div style="position:relative;min-height:660px;">
 
-<h3 style="color:#f80">EARLY ACCESS PREVIEW</h3>
+<h3 style="color:#FF4444;margin:-30px 0 20px">BETA</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,23 +185,21 @@
 
 
 
-
-
 <div id="main">
 
-<div class="figure" style="width:400px;margin-top:-20px">
-<img src="{@docRoot}images/tools/android-studio.png" height="330" width="400" style="margin-bottom:20px" />
+<div class="figure" style="width:400px;margin-top:-75px">
+<img src="{@docRoot}images/tools/laptop-studio.png" height="366" width="400" style="margin-bottom:20px" />
 
 <a class="big button subtitle" id="download-ide-button"
-href="" style="display:none;width:265px;margin:0 auto;display:block;font-size:18px" ></a>
-<div style="width:290px;padding:10px 40px 0 60px;font-size:12px;line-height:16px">
+href="" style="display:none;width:368px;margin:0 auto;display:block;font-size:18px" ></a>
+<div style="margin:20px 0 0 0">
 
 <p style="margin-bottom:8px">This download includes:</p>
-<ul>
-<li>Android Studio <b>early access preview</b></li>
-<li>All the Android SDK Tools to design, test, debug, and profile your app</li>
-<li>The latest Android platform to compile your app</li>
-<li>The latest Android system image to run your app in the emulator</li>
+<ul style="margin-bottom:20px">
+<li>Android Studio Beta</li>
+<li>All the Android SDK Tools to design, test, and debug your app</li>
+<li>A version of the Android platform to compile your app</li>
+<li>A version of the Android system image to run your app in the emulator</li>
 </ul>
 
 </div>
@@ -208,36 +208,34 @@
 
 
 <p>Android Studio is a new Android development environment based on IntelliJ
-IDEA. Similar to Eclipse with the
-ADT Plugin, Android Studio provides integrated Android developer tools
-for development and debugging. On top of the
+IDEA. It provides new features and improvements over Eclipse ADT
+and will be the official Android IDE once it's ready. On top of the
 capabilities you expect from IntelliJ, Android Studio offers:</p>
 
 <ul>
-  <li>Gradle-based build support.</li>
-  <li>Android-specific refactoring and quick fixes.</li>
-  <li>Lint tools to catch performance, usability, version compatibility and other problems.</li>
-  <li>ProGuard and app-signing capabilities. </li>
-  <li>Template-based wizards to create common Android designs and components.</li>
-  <li>A rich layout editor that allows you to drag-and-drop UI components, preview layouts on
-  multiple screen configurations, and much more.</li>
+  <li>Flexible Gradle-based build system.</li>
+  <li>Build variants and multiple APK generation.</li>
+  <li>Expanded template support for Google Services and various device types.</li>
+  <li>Rich layout editor with support for theme editing.</li>
+  <li>Lint tools to catch performance, usability, version compatibility, and other problems.</li>
+  <li>ProGuard and app-signing capabilities.</li>
   <li>Built-in support for <a
-  href="http://android-developers.blogspot.com/2013/06/adding-backend-to-your-app-in-android.html"
+  href="https://developers.google.com/cloud/devtools/android_studio_templates/"
   class="external-link">Google Cloud Platform</a>, making it easy to integrate Google Cloud
-  Messaging and App Engine as server-side components.
+  Messaging and App Engine.
 </ul>
 
-<p class="caution"><strong>Caution:</strong> Android Studio is currently available as
-an <strong>early access preview</strong>. Several features
-are either incomplete or not yet implemented and you may encounter bugs. If you are not
+<p class="caution"><strong>Caution:</strong> Android Studio is currently in
+<strong>beta</strong>. Some features
+are not yet implemented and you may encounter bugs. If you are not
 comfortable using an unfinished product, you may want to instead
-download (or continue to use) the
-<a href="{@docRoot}sdk/index.html">ADT Bundle</a> (Eclipse with the ADT Plugin).</p>
+download (or continue to use)
+<a href="{@docRoot}sdk/index.html">Eclipse with ADT</a>.</p>
 
 
-<h4 style="clear:right;text-align:right;margin-right:50px"><a href='' class="expandable"
+<h4 style="margin-top: 20px;"><a href='' class="expandable"
   onclick="toggleExpandable(this,'.pax');return false;"
-  >DOWNLOAD FOR OTHER PLATFORMS</a></h4>
+  >VIEW ALL DOWNLOADS AND SIZES</a></h4>
 
 <div class="pax col-13 online" style="display:none;margin:0;">
 
@@ -253,36 +251,36 @@
     <td>Windows</td>
     <td>
   <a onclick="return onDownload(this)" id="win-studio"
-      href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-windows.exe">
-      android-studio-bundle-135.1078000-windows.exe
+      href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-windows.exe">
+      android-studio-bundle-135.1245622-windows.exe
       </a>
     </td>
-    <td>519082997 bytes</td>
-    <td>ac69889210c4d02ee3ccc1c0f3c5cf3c</td>
+    <td>380000036 bytes</td>
+    <td>5720baef7d492f2df7398a38dae2fa92</td>
   </tr>
 
   <tr>
     <td><nobr>Mac OS X</nobr></td>
     <td>
   <a onclick="return onDownload(this)" id="mac-studio"
-    href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-mac.dmg">
-    android-studio-bundle-135.1078000-mac.dmg
+    href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-mac.dmg">
+    android-studio-bundle-135.1245622-mac.dmg
     </a>
     </td>
-    <td>495989974 bytes</td>
-    <td>8c7b1ef376b8ca206c99823d9e8fd54d</td>
+    <td>368451923 bytes</td>
+    <td>fa9da3625db44687576c5c4e8f96280e</td>
   </tr>
 
   <tr>
     <td>Linux</td>
     <td>
   <a onclick="return onDownload(this)" id="linux-studio"
-    href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-linux.tgz">
-    android-studio-bundle-135.1078000-linux.tgz
+    href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-linux.tgz">
+    android-studio-bundle-135.1245622-linux.tgz
     </a>
     </td>
-    <td>520523870 bytes</td>
-    <td>689238d5e632fd236b13f9c6d49f0cb4</td>
+    <td>417756987 bytes</td>
+    <td>c70dd2e4035484b84f0ad0046a34f136</td>
   </tr>
   </table>
 
@@ -290,6 +288,73 @@
 
 
 
+<h2 style="margin-bottom: 0px;">Android Studio vs. Eclipse ADT Comparison</h2>
+
+<p>
+The following table lists some key differences between Android Studio Beta and
+<a href="{@docRoot}sdk/index.html">Eclipse with ADT</a>.
+</p>
+
+<style>
+td.yes {
+  color: #669900;
+}
+td.no {
+  color: #CC0000;
+}
+</style>
+
+<table>
+  <tbody><tr>
+    <th>Feature</th>
+    <th>Android Studio</th>
+    <th>ADT</th>
+  </tr>
+  <tr>
+    <td>Build system</td>
+    <td><a href="http://www.gradle.org/" class="external-link">Gradle</a></td>
+    <td><a href="http://ant.apache.org/" class="external-link">Ant</a></td>
+    </tr>
+    <tr>
+
+    <td>Maven-based build dependencies</td>
+    <td class="yes">Yes</td>
+    <td class="no">No</td>
+    </tr>
+
+    <td>Build variants and multiple-APK generation (great for Android Wear)</td>
+    <td class="yes">Yes</td>
+    <td class="no">No</td>
+    </tr>
+
+    <tr>
+    <td>Advanced Android code completion and refactoring</td>
+    <td class="yes">Yes</td>
+    <td class="no">No</td>
+    </tr>
+  <tr>
+    <td>Graphical layout editor</td>
+    <td class="yes">Yes</td>
+    <td class="yes">Yes</td>
+   </tr>
+   <tr>
+  </tr>
+
+    <tr>
+    <td>APK signing and keystore management</td>
+    <td class="yes">Yes</td>
+    <td class="yes">Yes</td>
+    </tr>
+
+    <tr>
+    <td>NDK support</td>
+    <td class="no">Coming soon</td>
+    <td class="yes">Yes</td>
+    </tr>
+ </tbody></table>
+
+
+
 <h2 id="Updating">Updating from older versions</h2>
 
 <p>If you already have Android Studio installed, in most cases, you can upgrade to the latest
@@ -298,7 +363,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 +376,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>.
@@ -425,6 +398,18 @@
 <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=""/>Android Studio v0.8.0</a> <em>(June 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+    <p>Added support for Android Wear projects.</p>
+    <p>See <a href="http://tools.android.com/recent">tools.android.com</a> for a full list of changes.</p>
+  </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=""/>Android Studio v0.5.2</a> <em>(May 2014)</em>
   </p>
 
@@ -599,8 +584,8 @@
   if (os) {
     /* set up primary ACE download button */
     $('#download-ide-button').show();
-    $('#download-ide-button').append("Download Android Studio <span class='small'>v0.5.2</span>"
-        + "<br/> <span class='small'>for " + os + "</span>");
+    $('#download-ide-button').append("Download Android Studio Beta <span class='small'>v0.8.0</span>"
+        + "<br/> <span class='small'>with the Android SDK for " + os + "</span>");
     $('#download-ide-button').click(function() {return onDownload(this,true);}).attr('href', bundlename);
 
   } else {
@@ -642,9 +627,13 @@
 
   function onDownloadForRealz(link) {
     if ($("input#agree").is(':checked')) {
-      $("#tos").hide();
-      $("#main").show();
-      location.hash = "Updating";
+      $("h1").text('Now redirecting to the install instructions...');
+      $("#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..89b3857 100644
--- a/docs/html/tools/device.jd
+++ b/docs/html/tools/device.jd
@@ -5,6 +5,7 @@
 <div id="qv">
   <h2>In this document</h2>
   <ol>
+    <li><a href="#device-developer-options">Enabling On-device Developer Options</a></li>
     <li><a href="#setting-up">Setting up a Device for Development</a>
       <ol>
         <li><a href="#VendorIds">USB Vendor IDs</a></li>
@@ -43,6 +44,29 @@
 platform, in different screen sizes and orientations, and more.</p>
 
 
+<h2 id="developer-device-options" style="margin-bottom: 0px;">Enabling On-device Developer Options</h2>
+
+<img src="/images/tools/dev-options-inmilk.png" alt="" style="float:right;margin-left:30px">
+
+<p>Android-powered devices have a host of developer options that you can
+access on the phone, which let you:</p>
+<ul>
+  <li>Enable debugging over USB.</li>
+  <li>Quickly capture bug reports onto the device.</li>
+  <li>Show CPU usage on screen.</li>
+  <li>Draw debugging information on screen such as layout bounds,
+    updates on GPU views and hardware layers, and other information.</li>
+  <li>Plus many more options to simulate app stresses or enable debugging options.</li>
+</ul>
+<p>To access these settings, open the <em>Developer options</em> in the
+system Settings. On Android 4.2 and higher, the Developer options screen is
+hidden by default. To make it visible, go to
+<b>Settings &gt; About phone</b> and tap <b>Build number</b> seven times. Return to the previous
+screen to find Developer options at the bottom.</p>
+
+
+
+
 <h2 id="setting-up">Setting up a Device for Development</h2>
 
 <p>With an Android-powered device, you can develop and debug your Android applications just as you
@@ -280,6 +304,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/adb.jd b/docs/html/tools/help/adb.jd
index f980042..e2dd196 100644
--- a/docs/html/tools/help/adb.jd
+++ b/docs/html/tools/help/adb.jd
@@ -8,6 +8,7 @@
 <div id="qv">
   <h2>In this document</h2>
 <ol>
+  <li><a href="#Enabling">Enabling adb Debugging</a></li>
   <li><a href="#issuingcommands">Syntax</a></li>
   <li><a href="#commandsummary">Commands</a></li>
   <li><a href="#devicestatus">Querying for Emulator/Device Instances</a></li>
@@ -72,6 +73,19 @@
 instance from any client (or from a script).</p>
 
 
+<h2 id="Enabling">Enabling adb Debugging</h2>
+
+<p>In order to use adb with a device connected over USB, you must enable
+<strong>USB debugging</strong> in the device system settings, under <strong>
+Developer options</strong>.</p>
+
+<p>On Android 4.2 and higher, the Developer options screen is
+hidden by default. To make it visible, go to
+<b>Settings &gt; About phone</b> and tap <b>Build number</b> seven times. Return to the previous
+screen to find <strong>Developer options</strong> at the bottom.</p>
+
+<p>On some devices, the Developer options screen may be located or named differently.</p>
+
 <p class="note"><strong>Note:</strong> When you connect a device running Android 4.2.2 or higher
 to your computer, the system shows a dialog asking whether to accept an RSA key that allows
 debugging through this computer. This security mechanism protects user devices because it ensures
@@ -80,6 +94,11 @@
 SDK Platform-tools r16.0.1 and higher) in order to debug on a device running Android 4.2.2 or
 higher.</p>
 
+<p>For more information about connecting to a device over USB, read
+<a href="{@docRoot}tools/device.html">Using Hardware Devices</a>.</p>
+
+
+
 
 <h2 id="issuingcommands">Syntax</h2>
 
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/revisions/build-tools.jd b/docs/html/tools/revisions/build-tools.jd
index d8abab0..0abec2e 100644
--- a/docs/html/tools/revisions/build-tools.jd
+++ b/docs/html/tools/revisions/build-tools.jd
@@ -77,6 +77,25 @@
 <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=""/>Build Tools, Revision 20.0.0</a> <em>(June 2014)</em>
+  </p>
+  <div class="toggle-content-toggleme">
+
+    <dl>
+      <dt>General Notes:</dt>
+      <dd>
+        <ul>
+          <li>Added support for Android Wear.</li>
+        </ul>
+      </dd>
+    </dl>
+
+  </div>
+</div>
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
       alt=""/>Build Tools, Revision 19.1.0</a> <em>(May 2014)</em>
   </p>
   <div class="toggle-content-toggleme">
diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd
index 5d1d13b..b5495c2 100644
--- a/docs/html/tools/revisions/platforms.jd
+++ b/docs/html/tools/revisions/platforms.jd
@@ -47,6 +47,34 @@
 components will not be available for download.</p>
 
 
+<h2 id="4.4">Android 4.4W</h2>
+
+<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="" />Revision 1</a> <em>(June 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+
+    <p>Initial release for Android Wear.</p>
+    <p>Dependencies:</p>
+    <ul>
+      <li>Android SDK Platform-tools r20 or higher is required.</li>
+      <li>Android SDK Tools 23.0 or higher is required.</li>
+    </ul>
+  </div>
+
+  <h3>Device Definitions</h3>
+
+  <p>The platform includes the following device definitions for use in creating Android Virtual
+  Devices in the <a href="{@docRoot}tools/help/avd-manager.html">AVD Manager</a>:</p>
+
+  <ul>
+    <li>Android Wear Round (320 x 320, hdpi)</li>
+    <li>Android Wear Square (320 x 320, hdpi)</li>
+  </ul>
+</div>
 
 <h2 id="4.4">Android 4.4</h2>
 
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index 124e58d..8826ce3e 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -56,6 +56,38 @@
 <div class="toggle-content opened">
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+      alt=""/>ADT 23.0.0</a> <em>(June 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+<dl>
+  <dt>Dependencies:</dt>
+
+  <dd>
+    <ul>
+      <li>Java 7 or higher is required if you are targeting the L Developer Preview.</li>
+      <li>Java 1.6 or higher is required if you are targeting other releases.</li>
+      <li>Eclipse Indigo (Version 3.7.2) or higher is required.</li>
+      <li>This version of ADT is designed for use with
+        <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r23.0.0</a>.
+        If you haven't already installed SDK Tools r23.0.0 into your SDK, use the
+        Android SDK Manager to do so.</li>
+    </ul>
+  </dd>
+
+  <dt>General Notes:</dt>
+  <dd>
+    <ul>
+      <li>Added the Android Wear tools and system images.</li>
+    </ul>
+  </dd>
+</dl>
+</div>
+</div>
+
+<div class="toggle-content closed">
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
       alt=""/>ADT 22.6.3</a> <em>(April 2014)</em>
   </p>
 
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index 9b06a9d..6c74a71 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -28,6 +28,70 @@
 <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 23.0.0</a> <em>(June 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+
+    <dl>
+    <dt>Dependencies:</dt>
+
+    <dd>
+      <ul>
+        <li>Android SDK Platform-tools revision 19 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 23.0.0 and later. If you haven't already, update your
+        <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 23.0.0.</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>Added the Android Wear tools and system images.</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.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..93e5976 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -1,54 +1,38 @@
 <ul id="nav">
 
   <li class="nav-section">
-    <div class="nav-section-header empty">
-        <a href="<?cs var:toroot ?>tools/index.html"><span class="en">Developer Tools</span></a>
-    </div>
-  </li>
-
-  <li class="nav-section">
     <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/create-project.html">
+          Creating a Project</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">
         <a href="<?cs var:toroot ?>tools/workflow/index.html"><span class="en">Workflow</span></a>
@@ -174,7 +158,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 +234,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/app-indexing/index.jd b/docs/html/training/app-indexing/index.jd
index cb4135f..7e7241b 100644
--- a/docs/html/training/app-indexing/index.jd
+++ b/docs/html/training/app-indexing/index.jd
@@ -59,14 +59,6 @@
 </li>
 </ol>
 
-<p class="note"><strong>Note: </strong>
-Currently, the Google Search app indexing capability is restricted to
-English-only Android apps from developers participating in the early adopter
-program. You can sign up to be a participant by submitting the <a
-href="https://docs.google.com/a/google.com/forms/d/1itcqPAQqggJ6e4m8aejWLM8Dc5O8P6qybgGbKCNxGV0/viewform"
-class="external-link" target="_blank">App Indexing Expression of Interest</a> form.
-</p>
-
 <p>This class shows how to enable deep linking and indexing of your application
 content so that users can open this content directly from mobile search
 results.</p>
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/building-wearables.jd b/docs/html/training/building-wearables.jd
new file mode 100644
index 0000000..0745c93
--- /dev/null
+++ b/docs/html/training/building-wearables.jd
@@ -0,0 +1,10 @@
+page.title=Building Apps for Wearables
+page.trainingcourse=true
+page.image=wear/images/notifications.png
+page.metaDescription=Learn how to build notifications, send and sync data, and use voice actions.
+
+@jd:body
+
+
+<p>These classes teach you how to build notifications in a handheld app that are automatically
+synced to wearables as well as how to build apps that run on wearables.</p>
\ No newline at end of file
diff --git a/docs/html/training/cloudsync/backupapi.jd b/docs/html/training/cloudsync/backupapi.jd
index a5436c6..fd35ada 100644
--- a/docs/html/training/cloudsync/backupapi.jd
+++ b/docs/html/training/cloudsync/backupapi.jd
@@ -41,7 +41,7 @@
 
 <h2 id="register">Register for the Android Backup Service</h2>
 <p>This lesson requires the use of the <a
-  href="http://code.google.com/android/backup/index.html">Android Backup
+  href="{@docRoot}google/backup/index.html">Android Backup
   Service</a>, which requires registration.  Go ahead and <a
   href="http://code.google.com/android/backup/signup.html">register here</a>.  Once
 that's done, the service pre-populates an XML tag for insertion in your Android
diff --git a/docs/html/training/cloudsync/index.jd b/docs/html/training/cloudsync/index.jd
index eb7df3d..cf7117c 100644
--- a/docs/html/training/cloudsync/index.jd
+++ b/docs/html/training/cloudsync/index.jd
@@ -6,6 +6,16 @@
 
 @jd:body
 
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+<ul>
+  <li>Android 2.2 (API level 8) and higher</li>
+</ul>
+</div>
+</div>
+
 <p>By providing powerful APIs for internet connectivity, the Android framework
 helps you build rich cloud-enabled apps that sync their data to a remote web
 service, making sure all your devices always stay in sync, and your valuable
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..4407d30 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -609,7 +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>
   <!-- End connectivity and cloud -->
@@ -698,6 +726,90 @@
   <!-- End privacy and location -->
 
 
+  <li class="nav-section">
+    <div class="nav-section-header">
+      <a href="<?cs var:toroot ?>training/building-wearables.html">
+      <span class="small">Building Apps for</span><br/>
+              Wearables
+      </a>
+    </div>
+    <ul>
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>training/wearables/notifications/index.html"
+             description="How to build handheld notifications that are synced to
+             and look great on wearables."
+            >Adding Wearable Features to Notifications</a>
+        </div>
+        <ul>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/notifications/creating.html">Creating a Notification</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/notifications/voice-input.html">Receiving Voice Input in a Notification</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/notifications/pages.html">Adding Pages to a Notification</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/notifications/stacks.html">Stacking Notifications</a>
+          </li>
+        </ul>
+      </li>
+
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>training/wearables/apps/index.html"
+             description="How to build apps that run directly on wearables."
+            >Creating Wearable Apps</a>
+        </div>
+        <ul>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/apps/creating.html">Creating and Running a Wearable App</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/apps/layouts.html">Creating Custom Layouts</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/apps/voice.html">Adding Voice Capabilities</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/apps/packaging.html">Packaging Wearable Apps</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/apps/bt-debugging.html">Debugging over Bluetooth</a>
+          </li>
+        </ul>
+      </li>
+
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>training/wearables/data-layer/index.html"
+             description="How to sync data between handhelds and wearables."
+            >Sending and Syncing Data</a>
+        </div>
+        <ul>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/data-layer/accessing.html">Accessing the Wearable Data Layer</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/data-layer/data-items.html">Syncing Data Items</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/data-layer/assets.html">Transferring Assets</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/data-layer/messages.html">Sending and Receiving Messages</a>
+          </li>
+          <li>
+            <a href="<?cs var:toroot ?>training/wearables/data-layer/events.html">Handling Data Layer Events</a>
+          </li>
+        </ul>
+      </li>
+    </ul>
+  </li>
+
+  <!-- End Building for wearables -->
 
   <li class="nav-section">
     <div class="nav-section-header">
diff --git a/docs/html/training/tv/index.jd b/docs/html/training/tv/index.jd
index a99e378..54f7016 100644
--- a/docs/html/training/tv/index.jd
+++ b/docs/html/training/tv/index.jd
@@ -1,5 +1,5 @@
 page.title=Designing for TV
-page.tags=input,screens
+page.tags="input","screens"
 
 trainingnavtop=true
 startpage=true
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/html/training/wearables/apps/bt-debugging.jd b/docs/html/training/wearables/apps/bt-debugging.jd
new file mode 100644
index 0000000..8d09c43
--- /dev/null
+++ b/docs/html/training/wearables/apps/bt-debugging.jd
@@ -0,0 +1,92 @@
+page.title=Debugging over Bluetooth
+
+@jd:body
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+    <!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+    <h2>This lesson teaches you to</h2>
+    <ol>
+      <li><a href="#SetupDevices">Set Up Devices for Debugging</a></li>
+      <li><a href="#SetupSession">Set Up a Debugging Session</a></li>
+      <li><a href="#DebugApp">Debug Your App</a></li>
+    </ol>
+    <h2>You should also read</h2>
+    <ul>
+      <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+    </ul>
+  </div>
+</div>
+
+<p>You can debug your wearable over Bluetooth by routing it's debug output to the
+handheld device that's connected to your development machine.</p>
+
+<h2 id="SetupDevices">Setup Devices for Debugging</h2>
+<ol>
+  <li>Enable USB debugging on the handheld:
+    <ul>
+      <li>Open the Settings app and scroll to the bottom.</li>
+      <li>If it doesn't have a Developer Options setting, tap <b>About Phone</b>
+      (or <b>About Tablet</b>), scroll to the bottom, and tap the build number 7 times.</li>
+      <li>Go back and tap <b>Developer Options</b>.</li>
+      <li>Enable <b>USB debugging</b>.</li>
+    </ul>
+  </li>
+  <li>Enable Bluetooth debugging on the wearable:
+    <ol>
+      <li>Tap the home screen twice to bring up the Wear menu. </li>
+      <li>Scroll to the bottom and tap <b>Settings</b>.</li>
+      <li>Scroll to the bottom. If there's no <b>Developer Options</b> item, tap <b>About</b>,
+      and then tap the build number 7 times.</li>
+      <li>Tap the <b>Developer Options</b> item.</li>
+      <li>Enable <b>Debug over Bluetooth</b>.</li>
+    </ol>
+  </li>
+</ol>
+
+<h2 id="SetupSession">Set Up a Debugging Session</h2>
+<ol>
+ <li>On the handheld, open the Android Wear companion app.</li>
+ <li>Tap the menu on the top right and select <b>Settings</b>.</li>
+ <li>Enable <b>Debugging over Bluetooth</b>. You should see a tiny status summary appear under the
+ option:
+ <pre>
+Host: disconnected
+Target: connected
+</pre>
+</li>
+<li>Connect the handheld to your machine over USB and run:
+<pre>
+adb forward tcp:4444 localabstract:/adb-hub; adb connect localhost:4444
+</pre>
+
+<p class="note"><b>Note</b>: You can use any available port that you have access to.</p>
+</li>
+</ol>
+<p>
+In the Android Wear companion app, you should see the status change to:</p>
+<pre>
+Host: connected
+Target: connected
+</pre>
+
+<h2 id="#debug">Debug Your App</h2>
+
+Your wearable should show up as <code>localhost:4444</code> when running <code>adb devices</code>.
+
+To run any <code>adb</code> command, use this format:
+
+<pre>adb -s localhost:4444 &lt;command&gt; </pre>
+
+<p>If there are no other devices connected over TCP/IP (namely emulators),  you can shorten the command
+to:</p>
+<pre>
+adb -e &lt;command&gt;
+</pre>
+<p>For example:</p>
+<pre>
+adb -e logcat
+adb -e shell
+adb -e bugreport
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/creating.jd b/docs/html/training/wearables/apps/creating.jd
new file mode 100644
index 0000000..a5107d7
--- /dev/null
+++ b/docs/html/training/wearables/apps/creating.jd
@@ -0,0 +1,184 @@
+page.title=Creating and Running a Wearable App
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#SetupEmulator">Set Up an Android Wear Emulator</a></li>
+  <li><a href="#SetupDevice">Set Up an Android Wear Device</a></li>
+  <li><a href="#CreateProject">Create a Project</a></li>
+  <li><a href="#Libraries">Include the Correct Libraries</a></li>
+</ol>
+<h2>Dependencies and Prerequisites</h2>
+  <ul>
+    <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+  </ul>
+</div>
+</div>
+
+<p>Wearable apps run directly on the wearable device, giving you access to low-level
+hardware such as sensors, activities, services, and more, right
+on the wearable.</p>
+
+<p>A companion handheld app that contains the
+wearable app is also required when you want to publish to the Google Play store.
+Wearables don't support the Google Play store, so users download the companion handheld app,
+which automatically pushes the wearable app to the wearable. The handheld app is also
+useful for doing heavy processing, network actions, or other work and
+sending the results to the wearable.
+</p>
+
+<p>This lesson goes over how to set up a device or emulator and create one project to contain
+both your wearable and handheld apps.
+</p>
+
+
+<h2 id="SetupEmulator">Set Up an Android Wear Emulator or Device</h2>
+<p>We recommend that you develop on real hardware so you can better
+gauge the user experience. However, the emulator lets you test out different
+types of screen shapes, which is useful for testing.</p>
+
+<h3>Set up an Android Wear Virtual Device</h3>
+
+<p>To set up an Android Wear virtual device:</p>
+<ol>
+  <li>Click <b>Tools > Android > AVD Manager</b>.</li>
+  <li>Click <b>Create...</b>.</li>
+  <li>Fill in the following details for the AVD you want to specify and leave the rest
+  of the fields with their default values:
+    <ol>
+      <li><b>AVD Name</b> - A name for your AVD</li>
+      <li><b>Device</b> - Android Wear Round or Square device types</li>
+      <li><b>Target</b> - Android 4.4W - API Level 20</li>
+      <li><b>CPU/ABI</b> - Android Wear ARM (armeabi-v7a)</li>
+      <li><b>Keyboard</b> - Select <b>Hardware keyboard present</b></li>
+      <li><b>Skin</b> - AndroidWearRound or AndroidWearSquare depending on the selected device type</li>
+      <li><b>Snapshot</b> - Not selected</li>
+      <li><b>Use Host GPU</b> - Selected, to support custom activities for wearable notifications</li>
+    </ol>
+  </li>
+  <li>Click <b>OK</b>.</li>
+<li>Start the emulator:
+<ol>
+  <li>Select the virtual device you just created.</li>
+  <li>Click <b>Start...</b>, then click <b>Launch</b>.</li>
+  <li>Wait until the emulator initializes and shows the Android Wear home screen.</li>
+</ol>
+</li>
+<li>Pair Your handheld with the emulator:
+<ol>
+  <li>On your handheld, install the Android Wear app from Google Play.</li>
+  <li>Connect the handheld to your machine through USB.</li>
+  <li>Forward the AVD's communication port to the connected handheld device (you must
+  do this every time the handheld is connected):
+  <pre>adb -d forward tcp:5601 tcp:5601</pre>
+  </li>
+  <li>Start the Android Wear app on your handheld device and connect to the emulator.</li>
+  <li>Tap the menu on the top right corner of the Android Wear app and select
+  <b>Demo Cards</b>.</li>
+  <li>The cards you select appear as notifications on the home screen of the emulator.</li>
+</ol>
+</li>
+</ol>
+
+<h3 id="SetupDevice">Set Up an Android Wear Device</h3>
+<p>To set up an Android Wear device:</p>
+<ol>
+  <li>Install the Android Wear app, available on Google Play, on your handheld.</li>
+  <li>Follow the app's instructions to pair your handheld with your wearable.
+  This allows you to test out synced handheld notifications, if you're building them.</li>
+  <li>Connect the wearable to your machine through USB, so you can install apps directly to it
+  as you develop.</li>
+</ol>
+
+<h2 id="CreateProject">Create a Project</h2>
+
+<p>To begin development, create an app project that contains
+ wearable and handheld app modules. In Android Studio, click <b>File</b> >
+ <b>New Project</b> and follow the Project Wizard instructions, as described in
+ <a href="{@docRoot}sdk/installing/create-project.html">Creating a
+Project</a>. As you follow the wizard, enter the following information:</p>
+
+<ol>
+  <li>In the <b>Configure your Project</b> window, enter a name for your app and a package
+  name.</li>
+  <li>In the <b>Form Factors</b> window:
+    <ul>
+      <li>Select <b>Phone and Tablet</b> and select <b>API 8: Android 2.2 (Froyo)</b>
+      under <b>Minimum SDK</b>.</li>
+      <li>Select <b>Wear</b> and select <b>API 20: Android 4.4 (KitKat Wear)</b>
+      under <b>Minimum SDK</b>.</li>
+    </ul>
+  </li>
+  <li>In the first <b>Add an Activity</b> window, add a blank activity for mobile.</li>
+  <li>In the second <b>Add an Activity</b> window, add a blank activity for Wear.
+
+  <p>When the wizard completes, Android Studio creates a new project with two modules, <b>mobile</b> and
+  <b>wear</b>. You now have a project for both your handheld and wearable apps that you can create activities,
+  services, custom layouts, and much more in. On the handheld app, you do most of the heavy lifting,
+  such as network communications, intensive processing, or tasks that require long
+  amounts of user interaction. When these are done,
+  you usually notify the wearable of the results through notifications or by syncing and sending
+  data to the wearable.</p>
+
+  <p class="note"><b>Note:</b> The <b>wear</b> module also contains a "Hello World" activity that uses a
+  <code>WatchViewStub</code> that inflates a layout based on whether the device's screen
+  is round or square. The <code>WatchViewStub</code> class is one of the UI widgets that's provided
+  by the <a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary">wearable support library</a>.</p>
+</li>
+
+<h2 id="Install">Install the Wearable app</h2>
+
+<p>When developing, you install apps directly to the wearable like with handheld apps. Use
+either <code>adb install</code> or the <b>Play</b> button on Android Studio.</p>
+
+<p>When you're ready to publish your app to users, you embed the wearable app inside of the
+handheld app. When users install the handheld app from Google Play, a connected wearable automatically
+receives the wearable app.</p>
+
+<p class="note"><b>Note:</b> The automatic installation of wearable apps
+does not work when you are signing apps with a debug key and only works with release keys. See
+<a href="{@docRoot}traiing/wearables/packaging.html">Packaging Wearable Apps</a> for
+complete information on how to properly package wearable apps.</p>
+
+<li>
+To install the "Hello World" app to the wearable, select <b>wear</b> from the <b>Run/Debug
+configuration</b> drop-down menu and click the <b>Play</b> button. The activity shows up on the
+wearable and prints out "Hello world!"
+</li></ol>
+<h2 id="Libraries">Include the Correct Libraries</h2>
+
+<p>As part of the Project Wizard, the correct
+dependencies are imported for you in the appropriate module's <code>build.gradle</code> file.
+However, these dependencies are not required, so read the following descriptions to find out if you need them or not:</p>
+
+<b>Notifications</b>
+<p>The <a href="{@docRoot}tools/support-library/features.html#v4">The
+Android v4 support library</a> (or v13, which includes v4)
+contains the APIs to extend your existing notifications on handhelds to support wearables.</p>
+
+<p>For notifications that appear only on
+the wearable (meaning, they are issued by an app that runs on the wearable), you can just use the
+standard framework APIs (API Level 20) on the wearable and remove the support library
+dependency in the <b>mobile</b> module of your project.
+</p>
+
+<b>Wearable Data Layer</b>
+<p>To sync and send data between wearables and handhelds with the Wearable Data Layer APIs,
+you need the latest version of
+<a href="{@docRoot}google/play-services/setup.html">Google Play services</a>.
+If you're not using these APIs, remove the dependency from both modules.</p>
+
+<b>Wearable UI support library</b>
+<p>This is an unofficial library that includes UI widgets designed for wearables. We encourage you
+to use them in your apps, because they exemplify best practices, but they can still
+change at any time. However, if the libraries are updated, your apps won't break since they are compiled
+into your app. To get new features from an updated library, you just need to
+statically link the new version and update your app accordingly.
+This library is only applicable if you create wearable apps.
+</p>
+
+<p>In the next lessons, you'll learn how to create layouts designed for wearables as well as how
+to use the various voice actions that are supported by the platform.</p>
diff --git a/docs/html/training/wearables/apps/index.jd b/docs/html/training/wearables/apps/index.jd
new file mode 100644
index 0000000..a0d02fb
--- /dev/null
+++ b/docs/html/training/wearables/apps/index.jd
@@ -0,0 +1,74 @@
+page.title=Creating Wearable Apps
+page.image=wear/images/notifications.png
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+  <h2>Dependencies and Prerequisites</h2>
+  <ul>
+    <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+  </ul>
+</div>
+</div>
+
+<p>
+Wearable apps run directly on the device, giving you access to hardware such as sensors and the
+GPU. They are fundamentally the same as apps built for other devices using the Android SDK, but
+differ greatly in design and usability and the amount of functionality provided.
+These are the main differences between handheld and wearable apps:</p>
+
+<ul>
+  <li>The system enforces a timeout period. If you are displaying an activity and user's don't
+  interact with it, the device sleeps. When it wakes back up, the Wear home screen is displayed
+  instead of your activity. If you need to show something persistent, create a notification in the
+  context stream instead.</li>
+  <li>Wearable apps are relatively small in size and functionality compared to handheld apps.
+  They contain only what makes sense on the wearable, which is usually a small
+  subset of the corresponding handheld app. In general, you should carry out operations on the
+  handheld when possible and send the results to the wearable.
+  </li>
+  <li>Users don't download apps directly onto the wearable. Instead, you bundle
+  the wearable app inside the handheld app. When users install the handheld app,
+  the system automatically installs the wearable app. However, for development
+  purposes, you can still install the wearable app directly to the wearable.</li>
+  <li><p>Wearable apps can access much of the standard Android APIs, but don't support
+  the following APIs:</p>
+  <ul>
+    <li>{@link android.webkit}</li>
+    <li>{@link android.print}</li>
+    <li>{@link android.app.backup}</li>
+    <li>{@link android.appwidget}</li>
+    <li>{@link android.hardware.usb}</li>
+  </ul>
+  <p>
+  You can check if a wearable supports a feature by calling
+  {@link android.content.pm.PackageManager#hasSystemFeature hasSystemFeature()}
+  before trying to use an API.</p>
+</li>
+</ul>
+
+<p class="note"><b>Note:</b> We recommend using Android Studio for Android Wear development
+as it provides project setup, library inclusion, and packaging conveniences that aren't available
+in ADT. The rest of this training assumes you're using Android Studio.
+</p>
+
+<h2>Lessons</h2>
+  <dl>
+    <dt><a href="{@docRoot}training/wearables/apps/creating.html">Creating and Running a Wearable App</a></dt>
+      <dd>Learn how to create an Android Studio project that
+      contains both the wearable and handheld app modules and how to run the app on a device
+      or emulator.</dd>
+    <dt><a href="{@docRoot}training/wearables/apps/layouts.html">Creating Custom Layouts</a></dt>
+      <dd>Learn how to create and display custom layouts for notifications and
+      activities.</dd>
+    <dt><a href="{@docRoot}training/wearables/apps/voice.html">Adding Voice Capabilities</a></dt>
+      <dd>Learn how to launch an activity with a voice actions and how to start the
+      system speech recognizer app to obtain free-form voice input.</dd>
+    <dt><a href="{@docRoot}training/wearables/apps/packaging.html">Packaging Wearable Apps</a></dt>
+      <dd>Learn how to package a wearable app inside a
+      handheld app. This allows the system to install the wearable app automatically when
+      users install the companion handheld app from the Google Play store.</dd>
+    <dt><a href="{@docRoot}training/wearables/apps/packaging.html">Debugging over Bluetooth</a></dt>
+      <dd>Learn how to debug your wearable over Bluetooth instead of USB.</dd>
+  </dl>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/layouts.jd b/docs/html/training/wearables/apps/layouts.jd
new file mode 100644
index 0000000..4bf9cde
--- /dev/null
+++ b/docs/html/training/wearables/apps/layouts.jd
@@ -0,0 +1,134 @@
+page.title=Creating Custom Layouts
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#CustomNotifications">Create custom notifications</a></li>
+  <li><a href="#UiLibrary">Create Layouts with the Wearable UI Library</li>
+</ol>
+
+<!--STOPSHIP: link these -->
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>Creating layouts for wearables is the same as handheld devices, except you have to design
+for the screen size and for glanceability. Do not port functionality
+and the UI from a handheld app and expect a good experience. You should create custom layouts
+only when necessary. Read the <a href="{@docRoot}design/wear/index.html">design guidelines</a>
+for information on how to design great wearable apps.</p>
+
+<h2 id="CustomNotifications">Create Custom Notifications</h2>
+
+<p>
+In general, you should create notifications on the handheld and let them
+automatically sync to the wearable. This lets you build your notifications
+once and have them appear on many types of devices (not just wearables, but
+eventually Auto and TV) without having to design them for different
+form factors.</p>
+
+<p>If the standard notification styles don't work for you (such as
+{@link android.support.v4.app.NotificationCompat.BigTextStyle} or
+{@link android.support.v4.app.NotificationCompat.InboxStyle}), you can display an activity with
+a custom layout. You can only create and issue custom notifications on the wearable, and the
+system does not sync these notifications to the handheld.</p>
+
+<p clas="note"><b>Note:</b> When creating custom notifications on the wearable, you can use the
+standard notification APIs (API Level 20) instead of the Support Library.
+</p>
+
+<p>To create a custom notification:</p>
+<ol>
+  <li>Create a layout and set it as the content view for the activity
+  that you want to display.
+<pre>
+public void onCreate(Bundle bundle){
+    ...
+    setContentView(R.layout.notification_activity);
+}
+</pre>
+  </li>
+  <li>Define necessary properties for the activity in the Android manifest to allow
+  the activity to be displayed in the wearable's context stream process. You need to declare the
+  activity to be exportable, be embeddable, and have an empty task affinity. We also recommend
+  setting the theme to <code>Theme.DeviceDefault.Light</code>. For example:</li>
+<pre>
+&lt;activity android:name="com.example.MyDisplayActivity"
+     android:exported="true"
+     android:allowEmbedded="true"
+     android:taskAffinity=""
+     android:theme="@android:style/Theme.DeviceDefault.Light" /&gt;
+</pre>
+  </li>
+  <li>Create a {@link android.app.PendingIntent} for the activity that you want to display.
+  For example:
+<pre>
+Intent notificationIntent = new Intent(this, NotificationActivity.class);
+PendingIntent notificationPendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
+        PendingIntent.FLAG_UPDATE_CURRENT);
+</pre>
+  </li>
+  <li>Build a {@link android.app.Notification} and call
+  {@link android.app.Notification.WearableExtender#setDisplayIntent setDisplayIntent()}
+  providing the {@link android.app.PendingIntent}. The system uses this
+  {@link android.app.PendingIntent} to launch the activity when
+  users view your notification.
+  </li>
+  <li>Issue the notification using the
+  {@link android.app.NotificationManager#notify notify()} method.
+  <p class="note"><b>Note:</b> The system initially displays your notification with
+  a standard template. This template works well on all watchfaces. When users swipe the notification
+  up to view it, they'll then see your activity for the notification.</p>
+  </li>
+</ol>
+<h2 id="UiLibrary">Create Layouts with the Wearable UI Library</h2>
+<p>
+There's an unofficial UI library that is automatically included when you create your wearable
+app with the Android Studio Project Wizard. You can also add the library to your <code>build.gradle</code>
+file with the following dependency declaration:
+
+<pre>
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    <b>compile 'com.google.android.support:wearable:+'</b>
+    compile 'com.google.android.gms:play-services-wearable:+'
+}
+</pre>
+This library helps you build UIs that are designed for wearables. Here are some of the major classes:
+</p>
+
+<ul>
+    <li><code>BoxInsetLayout</code> - A FrameLayout that's aware of screen shape and can box its
+    children in the center square of a round screen.</li>
+    <li><code>CardFragment</code> - A fragment that presents content within an expandable,
+    vertically scrollable card.</li>
+    <li><code>CircledImageView</code> - An image view surrounded by a circle.</li>
+    <li><code>ConfirmationActivity</code> - An activity that displays confirmation animations after the user
+    completes an action.</li>
+    <li><code>DismissOverlayView</code> - A view for implementing long-press-to-dismiss.</li>
+    <li><code>GridViewPager</code> - A layout manager that allows the user to both vertically and
+    horizontally through pages of data. You supply an implementation of a GridPagerAdapter to
+    generate the pages that the view shows.</li>
+    <li><code>GridPagerAdapter</code> - An adapter that supplies pages to a GridViewPager.</li>
+    <li><code>FragmentGridPagerAdapter</code> - An implementation of GridPagerAdapter that
+    represents each page as a fragment.</li>
+    </li>
+    <li><code>WatchViewStub</code> - A class that can inflate a specific layout,
+    depending on the shape of the device's screen.</li>
+    <li><code>WearableListView</code> - An alternative version of ListView that is optimized for
+    ease of use on small screen wearable devices. It displays a vertically scrollable list of items,
+    and automatically snaps to the nearest item when the user stops scrolling.
+    </li>
+</ul>
+
+<p class="note"><a href="{@docRoot}shareables/training/wearable-support-docs.zip">Download the full API
+reference documentation</a> for the classes above. The documentation goes over how to use
+each UI widget.</p>
diff --git a/docs/html/training/wearables/apps/packaging.jd b/docs/html/training/wearables/apps/packaging.jd
new file mode 100644
index 0000000..ea29c9d
--- /dev/null
+++ b/docs/html/training/wearables/apps/packaging.jd
@@ -0,0 +1,156 @@
+page.title=Packaging Wearable Apps
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#Studio">Package with Android Studio</a></li>
+  <li><a href="#PackageManually">Package Manually</a></li>
+  <li><a href="#AssetCompression">Turn off Asset Compression</a></li>
+</ol>
+</div>
+</div>
+
+<p>When publishing to users, you must package a wearable app inside of a handheld app, 
+because users cannot browse and install apps directly on the wearable. If packaged properly,
+when users download the handheld app, the system automatically pushes the wearable app to the
+paired wearable.
+</p>
+
+<p class="note"><b>Note:</b> This feature doesn't work when you are signing your apps with
+a debug key when developing. While developing, installing apps with <code>adb install</code> or
+Android Studio directly to the wearable is required.</p>
+
+
+<h2 id="Studio">Package with Android Studio</h2>
+<p>To properly package a wearable app in Android Studio:</p>
+
+<ol>
+  <li>Declare a Gradle dependency in the handheld app's <code>build.gradle</code> file
+  that points to the wearable app module:
+<pre>
+dependencies {
+   compile 'com.google.android.gms:play-services:5.0.+@aar'
+   compile files('../../prebuilt-libs/android-support-v4.jar')
+   <b>wearApp project(':wearable')</b>
+}
+</pre>
+  </li>
+  <li>Click <b>Build > Generate Signed APK...</b> and follow the on-screen instructions
+  to specify your release keystore and sign your app. Android Studio exports the signed
+  handheld app with the wearable app embedded in it automatically into your project's root folder.
+
+  <p>Alternatively, you can create a <code>signingConfig</code> rule in the wearable and handheld
+  modules' <code>build.gradle</code> file to sign them with your release key. Both apps must be
+  signed to have the automatic pushing of the wearable app work.
+
+<pre>
+android {
+  ...
+  signingConfigs {
+    release {
+      keyAlias 'myAlias'
+      keyPassword 'myPw'
+      storeFile file('path/to/release.keystore')
+      storePassword 'myPw'
+    }
+  }
+  buildTypes {
+    release {
+      ...
+      signingConfig signingConfigs.release
+    }d
+  }
+  ...
+}
+</pre>
+  <p>Build the handheld app by clicking the Gradle button on the right vertical toolbar of
+  Android Studio and running the <b>assembleRelease</b> task. The task is located under
+  <b>Project name > Handheld module name > assembleRelease</b>.
+  </p>
+
+<p class="note"><b>Note:</b>This example embeds the password in your Gradle file, which might be undesirable. See
+<a href="{@docRoot}sdk/installing/studio-build.html#configureSigning">Configure signing settings</a>
+for information about how to create an environment variable for the passwords instead.
+</p>
+</ol>
+
+
+<h3>Signing the wearable and handheld app separately</h3>
+<p>If your build process requires signing the wearable app separately from the handheld app,
+you can declare the following Gradle rule in the handheld module's <code>build.gradle</code> to
+embed the previously-signed wearable app:</p>
+
+<pre>
+dependencies {
+  ...
+  wearApp files('/path/to/wearable_app.apk')
+}
+</pre>
+
+<p>You then sign your handheld app in any manner you wish (either with the Android Studio
+<b>Build > Generate Signed APK...</b> menu item or with Gradle <code>signingConfig</code> rules as
+described in the previous section.</p>
+
+<h2 id="PackageManually">Package Manually</h2>
+<p>
+It's still possible to package the wearable app into the handheld app manually
+if you are using another IDE or another method of building.
+</p>
+
+<ol>
+  <li>Copy the signed wearable app into your handheld project's <code>assets/</code> directory,
+  referred to as <code>path/to/wearable_app.apk</code>.</li>
+  <li>Create a <code>res/xml/wearable_app_desc.xml</code> file that contains the version and
+  path information of the wearable app:
+<pre>
+&lt;wearableApp package="com.google.android.wearable.myapp"&gt;
+  &lt;versionCode>1&lt;/versionCode&gt;
+  &lt;versionName>1.0&lt;/versionName&gt;
+  &lt;path>path/to/wearable_app.apk&lt;/path&gt;
+&lt;/wearableApp&gt;
+</pre>
+<p>
+The <code>package</code>, <code>versionCode</code>, and <code>versionName</code> are the
+same as values specified in the wearable app's <code>AndroidManifest.xml</code> file. 
+The path is the full path of <code>wearable_app.apk</code>, relative to the <code>assets/</code>
+directory.
+</p>
+</li>
+<li>
+Add a <code>meta-data</code> tag to your handheld app's <code>&lt;application&gt;</code> tag to
+reference the <code>wearable_app_desc.xml</code> file.
+<pre>
+  &lt;meta-data android:name="com.google.android.wearable.myapp" 
+                 android:resource="&#64;xml/wearable_app_desc"/&gt;
+</pre>
+</li>
+<li>Build and sign the handheld app.</li>
+</ol>
+
+<h2 id="AssetCompression">Turn off Asset Compression</h2>
+<p>
+Many build tools automatically compress any files added to the <code>assets/</code>
+directory of an Android app. Because the wearable APK is already zipped, these tools re-compress the
+wearable APK and the wearable app installer can no longer read the wearable app.
+</p>
+
+<p> When this happens, the installation fails. On the handheld app, the <code>PackageUpdateService</code>
+logs the following error: "this file cannot be opened as a file descriptor; it is probably compressed."
+
+<p> To prevent this error in Android Studio, update your handheld app's <code>build.gradle</code> file
+with the following declaration:
+</p>
+
+<pre>
+android {
+  aaptOptions {
+    noCompress "apk"
+  }
+}
+</pre>
+
+<p>If you are using another build process, ensure that you don't doubly compress the wearable app.</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/voice.jd b/docs/html/training/wearables/apps/voice.jd
new file mode 100644
index 0000000..3dea5d7
--- /dev/null
+++ b/docs/html/training/wearables/apps/voice.jd
@@ -0,0 +1,274 @@
+page.title=Adding Voice Capabilities
+@jd:body
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+    <!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+    <h2>This lesson teaches you to</h2>
+    <ol>
+      <li><a href="#SystemProvided">Declare System-provided Voice Actions</a></li>
+      <li><a href="#AppProvided">Declare App-provided Voice Actions</a></li>
+      <li><a href="#FreeFormSpeech">Obtaining Free-form Speech Input</a></li>
+    </ol>
+    <h2>You should also read</h2>
+    <ul>
+      <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+    </ul>
+  </div>
+</div>
+
+<p>Voice actions are an important part of the wearable experience. They let users carry
+out actions hands-free and quickly. Wear provides two types of voice actions:</p>
+
+<dl>
+  <dt><b>System-provided</b></dt>
+  <dd>These voice actions are task-based and are built
+  into the Wear platform. You filter for them in the activity that you want to start when the
+  voice action is spoken. Examples include "Take a note" or "Set an alarm".</dd>
+  <dt><b>App-provided</b></dt>
+  <dd>These voice actions are app-based, and you declare them just like a launcher icon.
+  Users say "Start <Your App Name>" to use these voice actions and an activity that you specify
+  starts.</dd>
+</dl>
+
+<h2 id="SystemProvided" style="clear:right">Declare System-provided Voice Actions</h2>
+<p>
+The Android Wear platform provides several voice intents that are based on user actions such
+as "Take a note" or "Set an alarm". This allows users to say what they want to do and let
+the system figure out the best activity to start.</p>
+
+<p>When users speak the voice action, your app can filter for the intent that is fired to start
+an activity. If you want to start a service to do something in the background, show an activity as
+a visual cue and start the service in the activity. Make sure to call
+{@link android.app.Activity#finish finish()} when you want to get rid of the visual cue.
+</p>
+
+<p>For example, for the "Take a note" command, declare this intent filter to start an activity
+named <code>MyNoteActivity</code>:
+</p>
+
+<pre>
+  &lt;activity android:name="MyNoteActivity"&gt;
+      &lt;intent-filter&gt;
+          &lt;action android:name="android.intent.action.SEND" /&gt;
+          &lt;category android:name="com.google.android.voicesearch.SELF_NOTE" /&gt;
+      &lt;/intent-filter&gt;
+  &lt;/activity&gt;
+</pre>
+
+<p>Here is a list of the voice intents supported by the Wear platform:</p>
+
+<table>
+  <tr>
+    <th>Name</th>
+    <th>Example Phrases</th>
+    <th>Intent</th>
+  </tr>
+
+  <tr>
+    <td>Call a car/taxi</td>
+    <td>"OK Google, get me a taxi"<br/><br/>"OK Google, call me a car"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd>
+          <code>com.google.android.gms.actions.RESERVE_TAXI_RESERVATION</code>
+        </dd>
+      </dl>
+    </td>
+  </tr>
+
+  <tr>
+    <td>Take a note</td>
+    <td>"OK Google, take a note"<br/><br/>"OK Google, note to self"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>android.intent.action.SEND</code></dd>
+        <dt>Category</dt>
+        <dd><code>com.google.android.voicesearch.SELF_NOTE</code></dd>
+        <dt>Extras</dt>
+        <dd><code>android.content.Intent.EXTRA_TEXT</code> - a string with note body</dd>
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Set alarm</td>
+    <td>"OK Google, set an alarm for 8 AM"<br/><br/>"OK Google, wake me up at 6 tomorrow"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>android.intent.action.SET_ALARM</code></dd>
+        <dt>Extras</dt>
+        <dd><code>android.provider.AlarmClock.EXTRA_HOUR</code> - an integer with the hour of
+        the alarm.
+        <p><code>android.provider.AlarmClock.EXTRA_MINUTES</code> -
+        an integer with the minute of the alarm
+        <p>(these 2 extras are optional, either none or
+        both are provided)</p></dd>
+
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Set timer</td>
+    <td>"Ok Google, set a timer for 10 minutes"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>android.provider.AlarmClock.ACTION_SET_TIMER</code></dd>
+        <dt>Extras</dt>
+        <dd><code>android.provider.AlarmClock.EXTRA_LENGTH</code> - an integer in the range of
+        1 to 86400 (number of seconds in 24 hours) representing the length of the timer </dd>
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Start/Stop a bike ride</td>
+    <td>"OK Google, start cycling"<br/><br/>"OK Google, start my bike ride"<br/><br/>"OK Google, stop cycling"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>vnd.google.fitness.TRACK</code></dd>
+        <dt>Mime Type</dt>
+        <dd><code>vnd.google.fitness.activity/biking</code></dd>
+        <dt>Extras</dt>
+        <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+        when starting and <code>CompletedActionStatus</code> when stopping.</dd>
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Start/Stop a run</td>
+    <td>"OK Google, track my run"<br/><br/>"OK Google, start running"<br/><br/>"OK Google, stop running"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>vnd.google.fitness.TRACK</code></dd>
+        <dt>MimeType</dt>
+        <dd><code>vnd.google.fitness.activity/running</code></dd>
+        <dt>Extras</dt>
+        <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+        when starting and <code>CompletedActionStatus</code> when stopping</dd>
+      </dl>
+   </td>
+  </tr>
+
+
+  <tr>
+    <td>Start/Stop a workout</td>
+    <td>"OK Google, start a workout"<br/><br/>"OK Google, track my workout"<br/><br/>"OK Google, stop workout"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>vnd.google.fitness.TRACK</code></dd>
+        <dt>MimeType</dt>
+        <dd><code>vnd.google.fitness.activity/other</code></dd>
+        <dt>Extras</dt>
+        <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+        when starting and <code>CompletedActionStatus</code> when stopping</dd>
+        </dd>
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Show heart rate</td>
+    <td>"OK Google, what’s my heart rate?"<br/><br/>"OK Google, what’s my bpm?"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>vnd.google.fitness.VIEW</code></dd>
+        <dt>Mime Type</dt>
+        <dd><code>vnd.google.fitness.data_type/com.google.heart_rate.bpm</code></dd>
+        </dd>
+      </dl>
+   </td>
+  </tr>
+
+  <tr>
+    <td>Show step count</td>
+    <td>"OK Google, how many steps have I taken?"<br/><br/>"OK Google, what’s my step count?"</td>
+    <td>
+      <dl>
+        <dt>Action</dt>
+        <dd><code>vnd.google.fitness.VIEW</code></dd>
+        <dt>Mime Type</dt>
+        <dd><code>vnd.google.fitness.data_type/com.google.step_count.cumulative</code></dd>
+        </dd>
+      </dl>
+   </td>
+  </tr>
+
+</table>
+
+<p>
+For documentation on registering for platform intents and accessing the extras information
+contained in them, see <a href="{@docRoot}guide/components/intents-common.html">Common intents</a>.
+</p>
+
+<h2 id="AppProvided">Declare App-provided Voice Actions</h2>
+<p>
+If none of the platform voice intents work for you, you can start your apps directly with
+a "Start MyActivityName" voice action. </p>
+
+<p>Registering for a "Start" action is the same as registering
+for a launcher icon on a handheld. Instead of requesting an app icon in a launcher,
+your app requests a voice action instead.</p>
+
+<p>To specify the text to say after "Start", specify a <code>label</code> attribute for the activtiy
+that you want to start. For example, this intent filter recognizes the
+"Start MyRunningApp" voice action and launches <code>StartRunActivity</code>.
+</p>
+
+<pre>
+&lt;application&gt;
+  &lt;activity android:name="StartRunActivity" android:label="MyRunningApp"&gt;
+      &lt;intent-filter&gt;
+          &lt;action android:name="android.intent.action.MAIN" /&gt;
+          &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+      &lt;/intent-filter&gt;
+  &lt;/activity&gt;
+&lt;/application&gt;
+</pre>
+
+<h2 id="FreeFormSpeech">Obtaining Free-form Speech Input</h2>
+<p>In addition to using voice actions to launch activities, you can also call the system's
+built-in Speech Recognizer activity to obtain speech input from users. This is useful to obtain input
+from users and then process it, such as doing a search or sending it as a message.</p>
+
+In your app, you call {@link android.app.Activity#startActivityForResult startActivityForResult()} using
+the {@link android.speech.RecognizerIntent#ACTION_RECOGNIZE_SPEECH} action. This starts the
+ and then handle the result
+in {@link android.app.Activity#onActivityResult onActivityResult()}.
+<pre>
+private static final int SPEECH_REQUEST_CODE = 0;
+
+// Create an intent that can start the Speech Recognizer activity
+private void displaySpeechRecognizer() {
+    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+            RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+// Start the activity, the intent will be populated with the speech text
+    startActivityForResult(intent, SPEECH_REQUEST_CODE);
+}
+
+// This callback is invoked when the Speech Recognizer returns.
+// This is where you process the intent and extract the speech text from the intent.
+&#64;Override
+protected void onActivityResult(int requestCode, int resultCode,
+        Intent data) {
+    if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
+        List&lt;String&gt; results = data.getStringArrayListExtra(
+                RecognizerIntent.EXTRA_RESULTS);
+        String spokenText = results.get(0);
+        // Do something with spokenText
+    }
+    super.onActivityResult(requestCode, resultCode, data);
+}
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/accessing.jd b/docs/html/training/wearables/data-layer/accessing.jd
new file mode 100644
index 0000000..4babd0a
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/accessing.jd
@@ -0,0 +1,59 @@
+page.title=Accessing the Wearable Data Layer
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li>Set up a Google Play services client to use the Wearable Data Layer APIs</li>
+</ol>
+
+<h2>Dependencies and Prerequisites</h2>
+<ol>
+  <li><a href="{@docRoot}training/wearables/apps/environment.html">Creating
+  Wearable Apps > Setting up Your Environment</a></li>
+  <li><a href="{@docRoot}training/wearables/apps/creating.html">Creating
+    Wearable Apps > Creating a Project</a></li>
+</ol>
+</div>
+</div>
+
+<p>To call the data layer API, create an instance of
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>,
+the main entry point for any of the Google Play services APIs.
+</p>
+
+<p>
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>
+provides a builder that makes it easy to create an instance of the client.
+A minimal <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a> looks like this:
+</p>
+
+<p class="note"><b>Note:</b> For now, this minimal client is enough to get started. However, see
+<a href="{@docRoot}google/auth/api-client.html">Accessing Google Play services APIs</a>
+for more information about creating a<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>,
+implementing its callbacks, and handling error cases.</p>
+
+<pre style="clear:right">
+GoogleApiClient mGoogleAppiClient = new GoogleApiClient.Builder(this)
+        .addConnectionCallbacks(new ConnectionCallbacks() {
+                &#64;Override
+                public void onConnected(Bundle connectionHint) {
+                    Log.d(TAG, "onConnected: " + connectionHint);
+                }
+                &#64;Override
+                public void onConnectionSuspended(int cause) {
+                    Log.d(TAG, "onConnectionSuspended: " + cause);
+                }
+        })
+        .addOnConnectionFailedListener(new OnConnectionFailedListener() {
+                &#64;Override
+                public void onConnectionFailed(ConnectionResult result) {
+                    Log.d(TAG, "onConnectionFailed: " + result);
+                }
+            })
+        .addApi(Wearable.API)
+        .build();
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/assets.jd b/docs/html/training/wearables/data-layer/assets.jd
new file mode 100644
index 0000000..73ebb73
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/assets.jd
@@ -0,0 +1,123 @@
+page.title=Transferring Assets
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#TransferAsset">Transfer an Asset</a></li>
+  <li><a href="#ReceiveAsset">Receive an Asset</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+To send large blobs of binary data over the Bluetooth transport, such as images, attach an
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html">Asset</a> to a
+data item and the put the data item into the replicated data store.
+</p>
+
+<p>Assets automatically handle caching of data to prevent retransmission and conserve Bluetooth bandwidth.
+A common pattern is for a handheld app to download an image, shrink it to an appropriate size
+for display on the wearable, and transmit it to the wearable app as an Asset. The following examples
+demonstrates this pattern.
+</p>
+
+<p class="note"><b>Note:</b> Although the size of data items are limited to 100KB,
+assets can be as large as desired. However, transferring large assets affect the
+user experience in many cases, so test your apps to ensure that they perform well
+if you're transferring large assets.
+<p>
+
+<h2 id="TransferAsset">Transfer an Asset</h2>
+<p>Create the asset using one of the <code>create...()</code> methods in the
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> class.
+Here, we convert a bitmap to a byte stream and then call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html#createFromBytes(byte[])"><code>createFromBytes()</code></a>
+to create the asset.
+</p>
+
+<pre>
+private static Asset createAssetFromBitmap(Bitmap bitmap) {
+    final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+    bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
+    return Asset.createFromBytes(byteStream.toByteArray());
+}
+</pre>
+
+<p>When you have an asset, attach it to a data item with the <code>putAsset()</code> method in
+
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html"><code>DataMap</code></a>
+or
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a>
+and then put the data item into the data store with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>putDataItem()</code></a>:
+</p>
+
+<p><b>Using PutDataRequest</b></p>
+<pre>
+Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
+Asset asset = createAssetFromBitmap(bitmap);
+PutDataRequest request = PutDataRequest.create("/image");
+request.putAsset("profileImage", asset);
+Wearable.DataApi.putDataItem(mGoogleApiClient, request);
+</pre>
+
+<p><b>Using PutDataMapRequest</b></p>
+<pre>
+Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
+Asset asset = createAssetFromBitmap(bitmap);
+PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
+dataMap.getDataMap().putAsset("profileImage", asset)
+PutDataRequest request = dataMap.asPutDataRequest();
+PendingResult&lt;DataApi.DataItemResult&gt; pendingResult = Wearable.DataApi
+        .putDataItem(mGoogleApiClient, request);
+</pre>
+
+<h2 id="ReceiveAsset">Receive assets</h2>
+
+<p>
+When an asset is created, you probably want to read and extract
+it on other side of the connection. Here's an example of how to implement the
+callback to detect an asset change and extract the Asset:
+</p>
+
+<pre>
+&#64;Override
+public void onDataChanged(DataEventBuffer dataEvents) {
+  for (DataEvent event : dataEvents) {
+    if (event.getType() == DataEvent.TYPE_CHANGED &&
+        event.getDataItem().getUri().getPath().equals("/image")) {
+      BundleDataItem bundleDataItem = BundleDataItem.fromDataItem(dataItem); 
+      Asset profileAsset = bundleDataItem.getData().getParcelable("profileImage");
+      Bitmap bitmap = loadBitmapFromAsset(profileAsset);
+      // Do something with the bitmap
+    }
+  }
+}
+
+public Bitmap loadBitmapFromAsset(Asset asset) {
+    if (asset == null) {
+        throw new IllegalArgumentException("Asset must be non-null");
+    }
+    ConnectionResult result =
+           mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    if (!result.isSuccess()) {
+        return null;
+    }
+    // convert asset into a file descriptor and block until it's ready
+    InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
+            mGoogleApiClient, asset).await().getInputStream();
+            mGoogleApiClient.disconnect();
+
+    if (assetInputStream == null) {
+        Log.w(TAG, "Requested an unknown Asset.");
+        return null;
+    }
+    // decode the stream into a bitmap
+    return BitmapFactory.decodeStream(assetInputStream);
+}
+</pre>
diff --git a/docs/html/training/wearables/data-layer/data-items.jd b/docs/html/training/wearables/data-layer/data-items.jd
new file mode 100644
index 0000000..c39e37a
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/data-items.jd
@@ -0,0 +1,124 @@
+page.title=Syncing Data Items
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#SyncData">Sync Data with a Data Map</a></li>
+  <li><a href="#ListenEvents">Listen for Data Item Events</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+A <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+defines the data interface that the system uses to synchronize data between handhelds
+and wearables. A <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a> generally
+consists of the following items:</p>
+<ul>
+  <li><b>Payload</b> - A byte array, which you can set with whatever data you wish, allowing you
+  to do your own object serialization and deserialization. The size of the payload is limited
+  to 100KB.</li>
+  <li><b>Path</b> - A unique string that must start with a forward slash (for instance,
+  <code>"/path/to/data"</code>)</li>
+</ul>
+
+<p>
+You normally don't implement <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+directly. Instead, you:
+
+<ol>
+  <li>Create a <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a> object,
+  specifying a string path to uniquely identify the item.
+  </li>
+  <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html#setData(byte[])">setData()</a> to set
+  the payload.
+  </li>
+  <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>DataApi.putDataItem()</code></a> to request the system to create the data item.
+  </li>
+  <li>When requesting data items, the system returns objects
+  that properly implement the <a href="{@docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a> interface.
+  </li>
+</ol>
+
+<p>
+However, instead of working with raw bytes using <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html#setData(byte[])">setData()</a>,
+we recommend you <a href="#data-map">use a data map</a>, which exposes
+a data item in an easy-to-use {@link android.os.Bundle}-like interface.
+</p>
+
+<h2 id="SyncData">Sync Data with a Data Map</h2>
+<p>
+When possible, use the <a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html"><code>DataMap</code></a> class,
+which lets you work with data items in the form of an Android {@link android.os.Bundle},
+so object serialization and de-serialization is done for you, and you can manipulate data with key-value pairs.
+</p>
+
+<p>To use a data map:</p>
+
+<ol>
+  <li>Create a
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html"><code>PutDataMapRequest</code></a>
+object, setting the path of the data item.
+<p class="note"><b>Note:</b> The path string is a unique identifier for the
+data item that allows you to access it from either side of the connection. The path must begin
+with a forward slash. If you're using hierarchical data in your
+app, you should create a path scheme that matches the structure of the data. 
+</p>
+</li>
+<li>Call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html#getDataMap()"><code>PutDataMapRequest.getDataMap()</code></a>
+</a> to obtain a data map that you can set values on.</li>
+  <li>Set any desired values for the data map using the <code>put...()</code> methods, such as
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html#putString(java.lang.String, java.lang.String)"><code>putString()</code></a>.
+  </li>
+  <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html#asPutDataRequest()"><code>PutDataMapRequest.asPutDataRequest()</code></a>
+  to obtain a <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a> object.
+   </li>
+  <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>DataApi.putDataItem()</code></a> to request the system to create the data item.
+  <p class="note"><b>Note:</b>
+  If the handset and wearable devices are disconnected,
+  the data is buffered and and synced when the connection is re-established.
+  </p>
+  </li>
+</ol>
+
+<p>The following example shows how to create a data map, set data on it, and create it:</p>
+
+<pre>
+PutDataMapRequest dataMap = PutDataMapRequest.create("/count");
+dataMap.getDataMap().putInt(COUNT_KEY, count++);
+PutDataRequest request = dataMap.asPutDataRequest();
+PendingResult&lt;DataApi.DataItemResult&gt; pendingResult = Wearable.DataApi
+        .putDataItem(mGoogleApiClient, request);
+</pre>
+
+<h2 id="ListenEvents">Listen for Data Item Events</h2>
+If one side of the data layer connection changes a data item, you probably want
+to be notified of any changes on the other side of the connection.
+You can do this by implementing a listener for data item events.
+
+<p>For example, here's what a typical callback looks like to carry out certain actions
+when data changes.</p>
+
+<pre>
+&#64;Override
+public void onDataChanged(DataEventBuffer dataEvents) {
+    for (DataEvent event : dataEvents) {
+        if (event.getType() == DataEvent.TYPE_DELETED) {
+            Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
+        } else if (event.getType() == DataEvent.TYPE_CHANGED) {
+             Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
+        }
+    }
+}
+</pre>
+<p>
+This is just a snippet that requires more implementation details. Learn about
+how to implement a full listener service or activity in
+<a href="{@docRoot}training/wearables/data-layer/listeners.html">Listening for Data Layer Events</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/events.jd b/docs/html/training/wearables/data-layer/events.jd
new file mode 100644
index 0000000..0146c4e
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/events.jd
@@ -0,0 +1,312 @@
+page.title=Handling Data Layer Events
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#Wait">Wait for the Status of Data Layer Calls</a></li>
+  <li><a href="#Listen">Listen for Data Layer Events</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>When you make calls with the data layer, you can receive the status
+of the call when it completes as well as listen for any changes that
+the call ends up making with listeners.
+</p>
+
+<h2 id="Wait">Wait for the Status of Data Layer Calls</h2>
+
+<p>You'll notice that calls to the data layer API sometimes return a
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a>,
+such as
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>putDataItem()</code></a>.
+As soon as the <a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a> is created,
+the operation is queued in the background. If you do nothing else after this, the operation
+eventually completes silently. However, you'll usually want to do something with the result
+after the operation completes, so the
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a>
+lets you wait for the result status, either synchronously or asynchronously.
+</p>
+
+<h3 id="async-waiting">Asynchronously waiting</h3>
+<p>If your code is running on the main UI thread, do not making blocking calls
+to the data layer API. You can run the calls asynchronously by adding a callback
+to the <a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a> object,
+which fires when the operation is completed:</p>
+<pre>
+pendingResult.setResultCallback(new ResultCallback&lt;DataItemResult&gt;() {
+    &#64;Override
+    public void onResult(final DataItemResult result) {
+        if(result.getStatus().isSuccess()) {
+        Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+    }
+});
+</pre>
+
+<h3 id="sync-waiting">Synchronously waiting</h3>
+<p>If your code is running on a separate handler thread in a background service (which is the case
+in a <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>),
+it's fine for the calls to block. In this case, you can call
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html#await()"><code>await()</code></a>
+on the PendingResult object, which will block until the request has completed, and return a Result
+object:
+</p>
+
+<pre>
+DataItemResult result = pendingResult.await();
+if(result.getStatus().isSuccess()) {
+    Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+}
+</pre>
+
+
+<h2 id="Listen">Listen for Data Layer Events </h2>
+<p>Because the data layer synchronizes and sends data across the handheld and
+wearable, you normally want to listen for important events, such as when data items
+are created, messages are received, or when the wearable and handset are connected.
+</p>
+<p>To listen for data layer events, you have two options:</p>
+
+<ul>
+  <li>Create a service that extends
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+  </li>
+  <li>Create an activity that implements
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a>.
+  </li>
+</ul>
+
+<p>With both these options, you override any of the data event callbacks that you care about
+handling in your implementation.</p>
+
+<h3 id="listener-service">With a WearableListenerService</h3>
+
+<p>
+You typically create instances of this service in both your wearable and handheld apps. If you
+don't care about data events in one of these apps, then you don't need to implement this
+service in that particular app.</p>
+
+<p>For example, you can have a handheld app that sets and gets data item objects and a wearable app
+that listens for these updates to update it's UI. The wearable never updates any of the data items,
+so the handheld app doesn't listen for any data events from the wearable app.</p>
+
+<p>You can listen for the following events with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:</p>
+
+<ul>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code></a>
+- Called when data item objects are created, changed, or deleted. An event on one side of a connection
+triggers an this callback on both sides.</li>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onMessageReceived(com.google.android.gms.wearable.MessageEvent)"><code>onMessageReceived()</code></a>
+-  A message sent from one side of a connection triggers this callback on the other side of the connection.</li>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onMessageReceived(com.google.android.gms.wearable.MessageEvent)"><code>onPeerConnected()</code></a>
+  and <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onPeerDisconnected(com.google.android.gms.wearable.Node)"><code>onPeerDisconnected()</code></a> -
+  Called when connection with the handheld or wearable is connected or disconnected. 
+  Changes in connection state on one side of the connection triggers these callbacks on both sides of the connection.
+  </li>
+</ul>
+
+<p>To create a <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:</p>
+
+<ol>
+  <li>Create a class that extends
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+  </li>
+  <li>Listen for the events that you care about, such as
+  <a href="{@docRoot}/reference/com/google/android/gms/wearable/WearableListenerService.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code></a>.
+  </li>
+  <li>Declare an intent filter in your Android manifest to notify the system about your
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+  This allows the system to bind your service as needed.
+  </li>
+</ol>
+
+  <p>The following example shows how to implement a simple
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:
+  </p>
+
+<pre>
+public class DataLayerListenerService extends WearableListenerService {
+
+    private static final String TAG = "DataLayerSample";
+    private static final String START_ACTIVITY_PATH = "/start-activity";
+    private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";
+
+    &#64;Override
+    public void onDataChanged(DataEventBuffer dataEvents) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onDataChanged: " + dataEvents);
+        }
+        final List<DataEvent> events = FreezableUtils
+                .freezeIterable(dataEvents);
+
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        ConnectionResult connectionResult =
+                googleApiClient.blockingConnect(30, TimeUnit.SECONDS);
+
+        if (!connectionResult.isSuccess()) {
+            Log.e(TAG, "Failed to connect to GoogleApiClient.");
+            return;
+        }
+
+        // Loop through the events and send a message
+        / to the node that created the data item.
+        for (DataEvent event : events) {
+            Uri uri = event.getDataItem().getUri();
+
+            // Get the node id from the host value of the URI
+            String nodeId = uri.getHost();
+            // Set the data of the message to be the bytes of the URI.
+            byte[] payload = uri.toString().getBytes();
+
+            // Send the RPC
+            Wearable.MessageApi.sendMessage(googleApiClient, nodeId,
+                    DATA_ITEM_RECEIVED_PATH, payload);
+        }
+    }
+}
+</pre>  
+
+<p>Here's the corresponding intent filter in the Android manifest file:</p>
+
+<pre>
+&lt;service android:name=".DataLayerListenerService"&gt;
+  &lt;intent-filter&gt;
+      &lt;action android:name="com.google.android.gms.wearable.BIND_LISTENER" /&gt;
+  &lt;/intent-filter&gt;
+&lt;/service&gt;
+</pre>
+
+
+<h4>Permissions within Data Layer Callbacks</h4>
+
+<p>In order to deliver callbacks to your application for data layer events, Google Play services
+binds to your <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>,
+and calls your callbacks via IPC. This has the consequence
+that your callbacks inherit the permissions of the calling process.</p>
+
+<p>If you try to perform a privileged operation within a callback, the security check fails because your callback is
+running with the identity of the calling process, instead of the identity of your app's
+process.</p>
+
+<p>To fix this, call {@link android.os.Binder#clearCallingIdentity} </a>,
+to reset identity after crossing the IPC boundary, and then restore identity with
+{@link android.os.Binder#restoreCallingIdentity restoreCallingIdentity()} when
+you've completed the privileged operation:
+</p>
+
+<pre>
+long token = Binder.clearCallingIdentity();
+try {
+    performOperationRequiringPermissions();
+} finally {
+    Binder.restoreCallingIdentity(token);
+}
+</pre>
+
+<h3 id="Listen">With a Listener Activity</h3>
+
+<p>
+If your app only cares about data layer events when the user is interacting
+with the app and does not need a long-running service to handle every data
+change, you can listen for events in an activity by implementing one or more
+of the following interfaces:
+
+<ul>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a></li>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.MessageListener.html"><code>MessageApi.MessageListener</code></a></li>
+  <li><a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html"><code>NodeApi.NodeListener</code></a></li>
+</ul>
+</p>
+
+<p>To create an activity that listens for data events:</p>
+<ol>
+<li>Implement the desired interfaces.</li>
+<li>In {@link android.app.Activity#onCreate}, create an instance of
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>
+to work with the data layer API.
+<li>
+In {@link android.app.Activity#onStart onStart()}, call <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html#connect()"><code>connect()</code></a> to connect the client to Google Play services.
+</li>
+<li>When the connection to Google Play services is established, the system calls
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)"><code>onConnected()</code></a>. This is where you call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.DataApi.DataListener)"><code>DataApi.addListener()</code></a>,
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.MessageApi.MessageListener)"><code>MessageApi.addListener()</code></a>,
+  or <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.NodeApi.NodeListener)"><code>NodeApi.addListener()</code></a>
+  to notify Google Play services that your activity is interested in listening for data layer events.
+</li>
+<li>In {@link android.app.Activity#onStop onStop()}, unregister any listeners with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.DataApi.DataListener)"><code>DataApi.removeListener()</code></a>,
+<a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.MessageApi.MessageListener)"><code>MessageApi.removeListener()</code></a>,
+or <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.NodeApi.NodeListener)"><code>NodeApi.removeListener()</code></a>.
+</li>
+<li>Implement <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code>,
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerConnected(com.google.android.gms.wearable.Node)"><code>onMessageReceived()</code></a>,
+    <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerConnected(com.google.android.gms.wearable.Node)"><code>onPeerConnected()</code></a>, and
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerDisconnected(com.google.android.gms.wearable.Node)"><code>onPeerDisconnected()</code></a>, depending on the interfaces that you implemented.
+</li>
+</ol>
+
+<p>Here's an example that implements
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a>:</p>
+
+<pre>
+public class MainActivity extends Activity implements
+        DataApi.DataListener, ConnectionCallbacks, OnConnectionFailedListener {
+
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.main);
+        mGoogleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .addConnectionCallbacks(this)
+                .addOnConnectionFailedListener(this)
+                .build();
+    }
+
+    &#64;Override
+    protected void onStart() {
+        super.onStart();
+        if (!mResolvingError) {
+            mGoogleApiClient.connect();
+        }
+    }
+
+   &#64;Override
+    public void onConnected(Bundle connectionHint) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Connected to Google Api Service");
+        }
+        Wearable.DataApi.addListener(mGoogleApiClient, this);
+    }
+
+    &#64;Override
+    protected void onStop() {
+        if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
+            Wearable.NodeApi.removeListener(mGoogleApiClient, this);
+            mGoogleApiClient.disconnect();
+        }
+        super.onStop();
+    }
+
+    &#64;Override
+    public void onDataChanged(DataEventBuffer dataEvents) {
+        for (DataEvent event : dataEvents) {
+            if (event.getType() == DataEvent.TYPE_DELETED) {
+                Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
+            } else if (event.getType() == DataEvent.TYPE_CHANGED) {
+                 Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
+            }
+        }
+    }
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/index.jd b/docs/html/training/wearables/data-layer/index.jd
new file mode 100644
index 0000000..3e06df1
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/index.jd
@@ -0,0 +1,87 @@
+page.title=Sending and Syncing Data
+@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 4.3 (API Level 18) or higher on the handset device</li>
+      <li>The latest version of <a href="{@docRoot}google/play">Google Play services</a></li>
+      <li>An Android Wear device or Wear AVD</li>
+    </ul>
+  </div>
+</div>
+
+<p>
+The Wearable Data Layer API, which is part of Google Play services, provides a communication channel
+for your handheld and wearable apps. The API consists of a set of data objects that the system can
+send and synchronize over the wire and listeners that notify your apps of important events with
+the data layer:</p>
+
+<dl>
+  <dt><b>Data Items</b></dt>
+  <dd>A <a href="{@docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+  provides data storage with automatic syncing between the handheld and
+  wearable.</dd>
+
+  <dt><b>Messages</b></dt>
+  <dd>The <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a> class
+  can send messages designed for "fire-and-forget" commands, such as controlling a handheld's
+  media player from the wearable or starting an intent on the wearable from the handheld.
+  The system always delivers the message when the handheld and wearable are connected and delivers
+  an error when the devices are disconnected. Messages are great for one-way requests or for a
+  request/response communication model.</dd>
+
+  <dt><b>Asset</b></dt>
+  <dd><a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> objects are for
+  sending binary blobs of data, such as images. You attach assets to data items and the system
+  automatically takes care of the transfer for you, conserving Bluetooth bandwidth by caching large assets
+  to avoid re-transmission.</dd>
+
+  <dt><b>WearableListenerService</b> (for services)</dt>
+  <dd><p>Extending <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>
+  lets you listen for important data layer events in a service. The system manages the lifecycle of
+  the <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>,
+  binding to the service when it needs to send data items or messages and unbinding the service when no work is needed.</p>
+  </dd>
+
+  <dt><b>DataListener</b> (for foreground activities)</dt>
+  <dd>
+  Implementing <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataListener</code></a>
+  in an activity lets you listen for important data layer events when an activity
+  is in the foreground. Using this instead of the
+  <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>
+  lets you listen for changes only when the user is actively using your app.
+  </dd>
+</dl>
+
+<p class="warning"><b>Warning:</b>
+Because these APIs are designed for communication between handhelds and wearables,
+these are the only APIs you should use to set up communication between these
+devices. For instance, don't try to open low-level sockets to create a communication
+channel.
+</p>
+
+<h2>Lessons</h2>
+  <dl>
+    <dt><a href="{@docRoot}training/wearables/data-layer/data-items.html">Accessing the Wearable Data Layer</a></dt>
+    <dd>This lesson shows you how to create a client to access the Data Layer APIs.</dd>
+
+    <dt><a href="{@docRoot}training/wearables/data-layer/data-items.html">Syncing Data Items</a></dt>
+    <dd>Data items are objects that are stored in a replicated data store that is automatically
+      synced from handhelds to wearables.</dd>
+
+    <dt><a href="{@docRoot}training/wearables/data-layer/assets.html">Transferring Assets</a></dt>
+      <dd>Assets are binary blobs of data that you typically use to transfer images or media.</dd>
+
+    <dt><a href="{@docRoot}training/wearables/data-layer/stacks.html">Sending and Receiving Messages</a></dt>
+      <dd>Messages are designed for fire-and-forget messages that you can send back and forth
+      between the wearable and handheld.</dd>
+
+    <dt><a href="{@docRoot}training/wearables/data-layer/events.html">Handling Data Layer Events</a></dt>
+      <dd>Be notified of changes and events to the data layer.</dd>
+  </dl>
+
+</div>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/messages.jd b/docs/html/training/wearables/data-layer/messages.jd
new file mode 100644
index 0000000..a9dec75
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/messages.jd
@@ -0,0 +1,99 @@
+page.title=Sending and Receiving Messages
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#SendMessage">Send a Message</a></li>
+  <li><a href="#ReceiveMessage">Receive a Message</a></li>
+</ol>
+</div>
+</div>
+
+<p>You send messages using the
+<a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a>
+and attach the following items to the message:</p>
+
+<ul>
+  <li>An arbitrary payload (optional)</li>
+  <li>A path that uniquely identifies the message's action</li>
+</ul>
+<p>
+Unlike data items, there is no syncing between the handheld and wearable apps.
+Messages are a one-way communication mechanism that's meant for
+"fire-and-forget" tasks, such as sending a message to the wearable
+to start an activity. You can also use messages in request/response model
+where one side of the connection sends a message, does some work,
+sends back a response message.</p>
+
+<h2 id="SendMessage">Send a Message</h2>
+
+<p>The following example shows how to send a message that indicates to the other
+side of the connect to start an activity.
+This call is made synchronously, which blocks until the message
+is received or when the request times out:
+</p>
+
+<p class="note"><b>Note:</b> Read more about asynchronous and synchronous calls
+to Google Play services and when to use each in
+<a href="google/auth/api-client.html#Communicating">Communicate with Google Play Services</a>.
+</p>
+
+<pre>
+Node node; // the connected device to send the message to
+GoogleApiClient mGoogleApiClient;
+public static final START_ACTIVITY_PATH = "/start/MainActivity";
+...
+
+    SendMessageResult result = Wearable.MessageApi.sendMessage(
+            mGoogleApiClient, node, START_ACTIVITY_PATH, null).await();
+    if (!result.getStatus().isSuccess()) {
+        Log.e(TAG, "ERROR: failed to send Message: " + result.getStatus());
+    }
+</pre>
+
+<p>
+Here's a simple way to get a list of connected nodes that you can potentially
+send messages to:</p>
+
+<pre>
+private Collection&lt;String&gt; getNodes() {
+    HashSet &lt;String&gt;results= new HashSet&lt;String&gt;();
+    NodeApi.GetConnectedNodesResult nodes =
+            Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
+    for (Node node : nodes.getNodes()) {
+        results.add(node.getId());
+    }
+    return results;
+}
+</pre>
+
+<h2 id="ReceiveMessage">Receiving a Message</h2>
+
+<p>
+
+To be notified of received messages, you implement a listener for message events.
+This example shows how you might do this by checking the <code>START_ACTIVITY_PATH</code>
+that the previous example used to send the message. If this condition is <code>true</code>,
+a specific activity is started.
+</p>
+
+<pre>
+&#64;Override
+public void onMessageReceived(MessageEvent messageEvent) {
+    if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
+        Intent startIntent = new Intent(this, MainActivity.class);
+        startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(startIntent);
+    }
+}
+</pre>
+
+<p>
+This is just a snippet that requires more implementation details. Learn about
+how to implement a full listener service or activity in
+<a href="#listening">Listening for Data Layer Events</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/creating.jd b/docs/html/training/wearables/notifications/creating.jd
new file mode 100644
index 0000000..a61fa07
--- /dev/null
+++ b/docs/html/training/wearables/notifications/creating.jd
@@ -0,0 +1,295 @@
+page.title=Creating a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#Import">Import the Necessary Classes</a></li>
+  <li><a href="#NotificationBuilder">Create Notifications with the Notification Builder</a></li>
+  <li><a href="#ActionButtons">Add Action Buttons</a></li>
+  <li><a href="#SpecifyWearableOnlyActions">Specify Wearable-only Actions</a></li>
+  <li><a href="#BigView">Add a Big View</a></li>
+  <li><a href="#AddWearableFeatures">Add Wearable Features for a Notification</a></li>
+  <li><a href="#Deliver">Deliver the Notification</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>To build handheld notifications that are also sent to wearables, use
+{@link android.support.v4.app.NotificationCompat.Builder}. When you build
+notifications with this class, the system takes care of displaying
+notifications properly, whether they appear on a handheld or wearable.
+</p>
+
+<p class="note"><strong>Note:</strong>
+Notifications using {@link android.widget.RemoteViews} are stripped of custom
+layouts and the wearable only displays the text and icons. However, you can create
+<a href="{@docRoot}training/wearables/apps/layouts.html#CustomNotifications">create custom notifications</a>
+that use custom card layouts by creating a wearable app that runs on the wearable device.</p>
+</div>
+
+<h2 id="Import">Import the necessary classes</h2>
+<p>Before you begin, import the necessary classes from the support library:</p>
+
+<pre style="clear:right">
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+</pre>
+
+<h2 id="NotificationBuilder">Create Notifications with the Notification Builder</h2>
+
+<p>The <a href="http://developer.android.com/tools/support-library/features.html#v4">v4
+support library</a> allows you to create notifications using the latest notification features
+such as action buttons and large icons, while remaining compatible with Android 1.6 (API level
+4) and higher.</p>
+
+<p>To create a notification with the support library, you create an instance of
+{@link android.support.v4.app.NotificationCompat.Builder} and issue the notification by
+passing it to {@link android.support.v4.app.NotificationManagerCompat#notify notify()}. For example:
+</p>
+
+<pre>
+int notificationId = 001;
+// Build intent for notification content
+Intent viewIntent = new Intent(this, ViewEventActivity.class);
+viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
+PendingIntent viewPendingIntent =
+        PendingIntent.getActivity(this, 0, viewIntent, 0);
+
+NotificationCompat.Builder notificationBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.ic_event)
+        .setContentTitle(eventTitle)
+        .setContentText(eventLocation)
+        .setContentIntent(viewPendingIntent);
+
+// Get an instance of the NotificationManager service
+NotificationManagerCompat notificationManager =
+        NotificationManagerCompat.from(this);
+
+// Build the notification and issues it with notification manager.
+notificationManager.notify(notificationId, notificationBuilder.build());
+</pre>
+
+<p>When this notification appears on a handheld device, the user can invoke the
+{@link android.app.PendingIntent}
+specified by the {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
+setContentIntent()} method by touching the notification. When this
+notification appears on an Android wearable, the user can swipe the notification to the left to
+reveal the <strong>Open</strong> action, which invokes the intent on the handheld device.</p>
+
+
+<img src="{@docRoot}wear/images/circle_email_action.png" height="200"
+  style="float:right;clear:right;margin:0 0 20px 60px" />
+
+<h2 id="ActionButtons">Add Action Buttons</h2>
+
+<p>In addition to the primary content action defined by
+{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
+setContentIntent()}, you can add other actions by passing a {@link android.app.PendingIntent} to
+the {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} method.</p>
+
+<p>For example, the following code shows the same type of notification from above, but adds an
+action to view the event location on a map.</p>
+
+<pre style="clear:right">
+// Build an intent for an action to view a map
+Intent mapIntent = new Intent(Intent.ACTION_VIEW);
+Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
+mapIntent.setData(geoUri);
+PendingIntent mapPendingIntent =
+        PendingIntent.getActivity(this, 0, mapIntent, 0);
+
+NotificationCompat.Builder notificationBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.ic_event)
+        .setContentTitle(eventTitle)
+        .setContentText(eventLocation)
+        .setContentIntent(viewPendingIntent)
+        <b>.addAction(R.drawable.ic_map,
+                getString(R.string.map), mapPendingIntent);</b>
+</pre>
+
+<p>On a handheld, the action appears as an
+additional button attached to the notification. On a wearable, the action appears as
+a large button when the user swipes the notification to the left. When the user taps the action,
+the associated intent is invoked on the handheld.</p>
+
+<p class="note"><strong>Tip:</strong> If your notifications include a "Reply" action
+  (such as for a messaging app), you can enhance the behavior by enabling
+  voice input replies directly from the Android wearable. For more information, read
+  <a href="{@docRoot}training/wearables/notifications/voice-input.html">Receiving Voice Input from
+  a Notification</a>.
+</p>
+
+<h2 id="SpecifyWearableOnlyActions">Specify Wearable-only Actions</h2>
+
+<p>
+If you want the actions available on the wearable to be different from those on the handheld,
+then use {@link android.support.v4.app.NotificationCompat.WearableExtender#addAction WearableExtender.addAction()}.
+Once you add an action with this method, the wearable does not display any other actions added with
+{@link android.support.v4.app.NotificationCompat.Builder#addAction NotificationCompat.Builder.addAction()}.
+That is, only the actions added with {@link android.support.v4.app.NotificationCompat.WearableExtender#addAction WearableExtender.addAction()} appear on the wearable and they do not appear on the handheld.
+</p>
+
+<pre>
+// Create an intent for the reply action
+Intent actionIntent = new Intent(this, ActionActivity.class);
+PendingIntent actionPendingIntent =
+        PendingIntent.getActivity(this, 0, actionIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+// Create the action
+NotificationCompat.Action action =
+        new NotificationCompat.Action.Builder(R.drawable.ic_action,
+                getString(R.string.label, actionPendingIntent))
+                .build();
+
+// Build the notification and add the action via WearableExtender
+Notification notification =
+        new NotificationCompat.Builder(mContext)
+                .setSmallIcon(R.drawable.ic_message)
+                .setContentTitle(getString(R.string.title))
+                .setContentText(getString(R.string.content))
+                .extend(new WearableExtender().addAction(action))
+                .build();
+</pre>
+<h2 id="BigView">Add a Big View</h2>
+
+<img src="{@docRoot}wear/images/06_images.png" height="200"
+  style="float:right;margin:0 0 20px 40px" />
+
+<p>You can insert extended text content
+to your notification by adding one of the "big view" styles to your notification. On a
+handheld device, users can see the big view content by expanding the notification. On
+a wearable device, the big view content is visible by default.</p>
+
+<p>To add the extended content to your notification, call {@link
+android.support.v4.app.NotificationCompat.Builder#setStyle setStyle()} on the {@link
+android.support.v4.app.NotificationCompat.Builder} object, passing it an instance of either
+{@link android.support.v4.app.NotificationCompat.BigTextStyle BigTextStyle} or
+{@link android.support.v4.app.NotificationCompat.InboxStyle InboxStyle}.</p>
+
+<p>For example, the following code adds an instance of
+{@link android.support.v4.app.NotificationCompat.BigTextStyle} to the event notification,
+in order to include the complete event description (which includes more text than can fit
+into the space provided for {@link android.support.v4.app.NotificationCompat.Builder#setContentText
+setContentText()}).</p>
+
+<pre style="clear:right">
+// Specify the 'big view' content to display the long
+// event description that may not fit the normal content text.
+BigTextStyle bigStyle = new NotificationCompat.BigTextStyle();
+bigStyle.bigText(eventDescription);
+
+NotificationCompat.Builder notificationBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.ic_event)
+        .setLargeIcon(BitmapFractory.decodeResource(
+                getResources(), R.drawable.notif_background))
+        .setContentTitle(eventTitle)
+        .setContentText(eventLocation)
+        .setContentIntent(viewPendingIntent)
+        .addAction(R.drawable.ic_map,
+                getString(R.string.map), mapPendingIntent)
+        <b>.setStyle(bigStyle);</b>
+</pre>
+
+<p>Notice that you can add a large background image to any notification using the
+{@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon setLargeIcon()}
+method. For more information about designing notifications with large images, see the
+<a href="{@docRoot}design/wear/index.html">Design Principles of Android
+Wear</a>.</p>
+
+<h2 id="AddWearableFeatures">Add Wearable Features For a Notification</h2>
+
+<p>If you ever need to add wearable-specific options to a notification, such as specifying additional
+pages of content or letting users dictate a text response with voice input, you can use the
+{@link android.support.v4.app.NotificationCompat.WearableExtender} class to
+specify the options. To use this API:</p>
+
+<ol>
+  <li>Create an instance of a {@link android.support.v4.app.NotificationCompat.WearableExtender WearableExtender},
+  setting the wearable-specific options for the notication.</li>
+  <li>Create an instance of
+  {@link android.support.v4.app.NotificationCompat.Builder}, setting the
+  desired properties for your notification as described earlier in this lesson.</li>
+  <li>Call {@link android.support.v4.app.NotificationCompat.Builder#extend extend()} on
+  the notification and pass in the
+  {@link android.support.v4.app.NotificationCompat.WearableExtender WearableExtender}. This applies
+  the wearable options to the notification.</li>
+  <li>Call {@link android.support.v4.app.NotificationCompat.Builder#build} to build the notification.</li>
+</ol>
+
+<p>
+For example, the following code calls the
+{@link android.support.v4.app.NotificationCompat.WearableExtender#setHintHideIcon setHintHideIcon()}
+method to remove the app icon from the notification card.
+</p>
+
+<pre>
+// Create a WearableExtender to add functionality for wearables
+NotificationCompat.WearableExtender wearableExtender =
+        new NotificationCompat.WearableExtender()
+        .setHintHideIcon(true);
+
+// Create a NotificationCompat.Builder to build a standard notification
+// then extend it with the WearableExtender
+Notification notif = new NotificationCompat.Builder(mContext)
+        .setContentTitle("New mail from " + sender)
+        .setContentText(subject)
+        .setSmallIcon(R.drawable.new_mail);
+        .extend(wearableExtender)
+        .build();
+</pre>
+
+<p>The
+  {@link android.support.v4.app.NotificationCompat.WearableExtender#setHintHideIcon setHintHideIcon()}
+  method is just one example of new notification features available with
+  {@link android.support.v4.app.NotificationCompat.WearableExtender}.
+</p>
+
+<p>If you ever need to read wearable-specifc options at a later time, use the corresponding get
+method for the option. This example calls the
+{@link android.support.v4.app.NotificationCompat.WearableExtender#getHintHideIcon()} method to
+get whether or not this notification hides the icon:
+<pre>
+NotificationCompat.WearableExtender wearableExtender =
+        new NotificationCompat.WearableExtender(notif);
+boolean hintHideIcon = wearableExtender.getHintHideIcon();
+</pre>
+
+<h2 id="Deliver">Deliver the Notification</h2>
+<p>When you want to deliver your notifications, always use the
+  {@link android.support.v4.app.NotificationManagerCompat} API instead of
+  {@link android.app.NotificationManager}:</p>
+
+<pre>
+// Get an instance of the NotificationManager service
+NotificationManagerCompat notificationManager =
+        NotificationManagerCompat.from(mContext);
+
+// Issue the notification with notification manager.
+notificationManager.notify(notificationId, notif);
+</pre>
+
+<p>If you use the framework's {@link android.app.NotificationManager}, some
+features from {@link android.support.v4.app.NotificationCompat.WearableExtender}
+do not work, so make sure to use {@link android.support.v4.app.NotificationCompat}.
+</p>
+
+<pre>
+NotificationCompat.WearableExtender wearableExtender =
+        new NotificationCompat.WearableExtender(notif);
+boolean hintHideIcon = wearableExtender.getHintHideIcon();
+ </pre>
+
+<p>The {@link android.support.v4.app.NotificationCompat.WearableExtender} APIs allow you to add
+additional pages to notifications, stack notifications, and more. Continue to the following lessons
+to learn about these features.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/index.jd b/docs/html/training/wearables/notifications/index.jd
new file mode 100644
index 0000000..17f3cb3
--- /dev/null
+++ b/docs/html/training/wearables/notifications/index.jd
@@ -0,0 +1,51 @@
+page.title=Adding Wearable Features to Notifications
+@jd:body
+
+<div id="tb-wrapper">
+  <div id="tb">
+
+    <h2>Dependencies and prerequisites</h2>
+    <ul>
+      <li>Android 4.3 (API Level 18) or higher on the handset device</li>
+      <li>An Android Wear device or Wear AVD</li>
+      <li><a href="{@docRoot}tools/support-library/features.html#v4">The Android v4 support library
+      (or v13, which includes v4)</li>
+    </ul>
+    <h2>You should also read</h2>
+    <ul>
+      <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+    </ul>
+  </div>
+</div>
+
+<p>When an Android handheld (phone or tablet) and Android wearable are connected, the handheld
+automatically shares notifications with the wearable. On the wearable, each
+notification appears as a new card in the <a href="{@docRoot}design/wear/index.html"
+>context stream</a>.</p>
+
+<p>However, to give users the best experience, you should add wearable-specific functionality to the
+notifications you already create. The following lessons show you how to
+create notifications that are catered for handhelds and wearables at the same time.
+</p>
+
+<img src="{@docRoot}wear/images/notification_phone@2x.png" width="700" height="265" />
+
+<p class="caption"><b>Figure 1.</b> The same notification displayed on a handheld and wearable.</p>
+
+<h2>Lessons</h2>
+  <dl>
+    <dt><a href="{@docRoot}training/wearables/notifications/creating.html">Creating a notification</a></dt>
+      <dd>Learn how to create notifications with wearable features with the Android support library.</dd>
+    <dt><a href="{@docRoot}training/wearables/notifications/voice-input.html">Receiving Voice Input
+in a Notification</a></dt>
+      <dd>Learn how to add an action to a wearable notification that receives voice input from users and delivers
+      the transcribed message to your handset app.</dd>
+    <dt><a href="{@docRoot}training/wearables/notifications/pages.html">Adding Pages to a Notification</a></dt>
+      <dd>Learn how to add additional pages of information that are visible when the user
+swipes to the left.</dd>
+    <dt><a href="{@docRoot}training/wearables/notifications/stacks.html">Stacking Notifications</a></dt>
+      <dd>Learn how to place all similar notifications from your app in a stack, allowing users to view
+      each notification individually without adding multiple cards to the card stream.</dd>
+  </dl>
+
+</div>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/pages.jd b/docs/html/training/wearables/notifications/pages.jd
new file mode 100644
index 0000000..d74c8ea
--- /dev/null
+++ b/docs/html/training/wearables/notifications/pages.jd
@@ -0,0 +1,71 @@
+page.title=Adding Pages to a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li>Add Pages to a Notification</li>
+</ol>
+</div>
+</div>
+
+<p>When you'd like to provide more information without requiring users
+to open your app on their handheld device, you can
+add one or more pages to the notification on the wearable. The additional pages
+appear immediately to the right of the main notification card.
+</p>
+
+<img src="{@docRoot}wear/images/09_pages.png" height="200" style="margin:0 0 20px 0" />
+<img src="{@docRoot}wear/images/08_pages.png" height="200" style="margin:0 0 20px 40px" />
+
+<p>To create a notification with multiple pages:</p>
+<ol>
+    <li>Create the main notification (the first page) with
+    {@link android.support.v4.app.NotificationCompat.Builder},
+    in the way you'd like the notification to appear on a handset.</li>
+    <li>Create the additional pages for the wearable with
+    {@link android.support.v4.app.NotificationCompat.Builder}.</li>
+    <li>Apply the pages to the main notification with the
+    {@link android.support.v4.app.NotificationCompat.WearableExtender#addPage addPage()}
+    method or add multiple pages in a {@link java.util.Collection} with the
+    {@link android.support.v4.app.NotificationCompat.WearableExtender#addPage addPages()} method.
+    </li>
+</ol>
+
+<p>For example, here's some code that adds a second page to a notification:</p>
+
+<pre>
+// Create builder for the main notification
+NotificationCompat.Builder notificationBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.new_message)
+        .setContentTitle("Page 1")
+        .setContentText("Short message")
+        .setContentIntent(viewPendingIntent);
+
+// Create a big text style for the second page
+BigTextStyle secondPageStyle = new NotificationCompat.BigTextStyle();
+secondPageStyle.setBigContentTitle("Page 2")
+               .bigText("A lot of text...");
+
+// Create second page notification
+Notification secondPageNotification =
+        new NotificationCompat.Builder(this)
+        .setStyle(secondPageStyle)
+        .build();
+
+// Add second page with wearable extender and extend the main notification
+Notification twoPageNotification =
+        new WearableExtender()
+                .addPage(secondPageNotification)
+                .extend(notificationBuilder)
+                .build();
+
+// Issue the notification
+notificationManager =
+        NotificationManagerCompat.from(this);
+notificationManager.notify(notificationId, twoPageNotification);
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/stacks.jd b/docs/html/training/wearables/notifications/stacks.jd
new file mode 100644
index 0000000..e71e74c
--- /dev/null
+++ b/docs/html/training/wearables/notifications/stacks.jd
@@ -0,0 +1,154 @@
+page.title=Stacking Notifications
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#AddGroup">Add Each Notification to a Group</a></li>
+  <li><a href="#AddSummary">Add a Summary Notification</a></li>
+</ol>
+
+</div>
+</div>
+<img src="/wear/images/11_bundles_B.png" height="200" width="169"
+    style="margin:0 0 20px 20px; clear:both; float:right" alt="">
+<img src="/wear/images/11_bundles_A.png" height="200" width="169"
+    style="margin:0 20px 20px 20px; float:right" alt="">
+<p>When creating notifications for a handheld device, you should always aggregate similar
+notifications into a single summary notification. For example, if your app creates notifications
+for received messages, you should not show more than one notification
+on a handheld device&mdash;when more than one is message is received, use a single notification
+to provide a summary such as "2 new messages."</p>
+
+<p>However, a summary notification is less useful on an Android wearable because users
+are not able to read details from each message on the wearable (they must open your app on the
+handheld to view more information). So for the wearable device, you should
+group all the notifications together in a stack. The stack of notifications appears as a single
+card, which users can expand to view the details from each notification separately. The new
+{@link android.support.v4.app.NotificationCompat.Builder#setGroup setGroup()} method makes this
+possible while allowing you to still provide only one summary notification on the handheld device.</p>
+
+<h2 id="AddGroup" style="clear:right">Add Each Notification to a Group</h2>
+
+<p>To create a stack, call {@link android.support.v4.app.NotificationCompat.Builder#setGroup setGroup()}
+for each notification you want in the stack and specify a
+group key. Then call {@link android.support.v4.app.NotificationManagerCompat#notify notify()}
+to send it to the wearable.</p>
+
+<pre style="clear:right">
+final static String GROUP_KEY_EMAILS = "group_key_emails";
+
+// Build the notification, setting the group appropriately
+Notification notif = new NotificationCompat.Builder(mContext)
+         .setContentTitle("New mail from " + sender1)
+         .setContentText(subject1)
+         .setSmallIcon(R.drawable.new_mail);
+         .setGroup(GROUP_KEY_EMAILS)
+         .build();
+
+// Issue the notification
+NotificationManagerCompat notificationManager =
+        NotificationManagerCompat.from(this);
+notificationManager.notify(notificationId1, notif);
+</pre>
+
+<p>Later on, when you create another notification, specify
+the same group key. When you call
+{@link android.support.v4.app.NotificationManagerCompat#notify notify()},
+this notification appears in the same stack as the previous notification,
+instead of as a new card:</p>
+
+<pre style="clear:right">
+Notification notif2 = new NotificationCompat.Builder(mContext)
+         .setContentTitle("New mail from " + sender2)
+         .setContentText(subject2)
+         .setSmallIcon(R.drawable.new_mail);
+         .setGroup(GROUP_KEY_EMAILS)
+         .build();
+
+notificationManager.notify(notificationId2, notif2);
+</pre>
+
+<p>By default, notifications appear in the order in which you added them, with the most recent
+  notification visible at the top.  You can order notifications in another fashion by calling
+  {@link android.support.v4.app.NotificationCompat.Builder#setSortKey setSortKey()}.</p>
+
+
+<h2 id="AddSummary">Add a Summary Notification</h2>
+
+<img src="{@docRoot}wear/images/notif_summary_framed.png" height="242" width="330" style="float:right;margin:0 0 20px 40px" alt="" />
+
+<p>It's important that you still provide a summary notification that appears on handheld devices.
+So in addition to adding each unique notification to the same stack group, also add a summary
+notification and call {@link android.support.v4.app.NotificationCompat.Builder#setGroupSummary setGroupSummary()}
+on the summary notification.</p>
+
+<p>This notification does not appear in your stack of notifications on the wearable, but
+appears as the only notification on the handheld device.</p>
+
+<pre style="clear:right">
+Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
+        R.drawable.ic_large_icon);
+
+// Create an InboxStyle notification
+Notification summaryNotification = new NotificationCompat.Builder(mContext)
+        .setContentTitle("2 new messages")
+        .setSmallIcon(R.drawable.ic_small_icon)
+        .setLargeIcon(largeIcon)
+        .setStyle(new NotificationCompat.InboxStyle()
+                .addLine("Alex Faaborg   Check this out")
+                .addLine("Jeff Chang   Launch Party")
+                .setBigContentTitle("2 new messages")
+                .setSummaryText("johndoe@gmail.com"))
+        .setGroup(GROUP_KEY_EMAILS)
+        .setGroupSummary(true)
+        .build();
+
+notificationManager.notify(notificationId3, summaryNotification);
+</pre>
+
+<p>
+This notification uses {@link android.support.v4.app.NotificationCompat.InboxStyle},
+which gives you an easy way to create notifications for email or messaging apps.
+You can use this style, another one defined in {@link android.support.v4.app.NotificationCompat},
+or no style for the summary notification.
+</p>
+
+<p class="note"><b>Tip:</b>
+To style the text like in the example screenshot, see
+<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithHTML">Styling
+with HTML markup</a> and
+<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithSpannables">Styling
+with Spannables</a>.
+</p>
+
+<p>Summary notifications can also affect notifications on wearables without being displayed on them.
+When creating a summary notification, you can use the
+{@link android.support.v4.app.NotificationCompat.WearableExtender} class and call
+{@link android.support.v4.app.NotificationCompat.WearableExtender#setBackground setBackground()} or
+{@link android.support.v4.app.NotificationCompat.WearableExtender#addAction addAction()} to set
+a background image or an action that applies to the entire stack on the wearable. For instance,
+to set the background for an entire stack of notifications:
+</p>
+
+<pre>
+Bitmap background = BitmapFactory.decodeResource(getResources(),
+        R.drawable.ic_background);
+
+NotificationCompat.WearableExtender wearableExtender =
+        new NotificationCompat.WearableExtender()
+        .setBackground(background);
+
+// Create an InboxStyle notification
+Notification summaryNotificationWithBackground =
+        new NotificationCompat.Builder(mContext)
+        .setContentTitle("2 new messages")
+        ...
+        .extend(wearableExtender)
+        .setGroup(GROUP_KEY_EMAILS)
+        .setGroupSummary(true)
+        .build();
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/voice-input.jd b/docs/html/training/wearables/notifications/voice-input.jd
new file mode 100644
index 0000000..4a27826
--- /dev/null
+++ b/docs/html/training/wearables/notifications/voice-input.jd
@@ -0,0 +1,175 @@
+page.title=Receiving Voice Input in a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#VoiceInput">Define the Voice Input</a></li>
+  <li><a href="#AddAction">Add the Voice Input as a Notification Action</li>
+  <li><a href="#ReceiveInput">Receiving the Voice Input as a String</a>
+</ol>
+
+</div>
+</div>
+
+<p>If you have handheld notifications that include an action to input text,
+such as reply to an email, it should normally launch an activity
+on the handheld device to input the text. However, when your notification appears on a wearable,
+there is no keyboard input, so you can let users dictate a reply or provide pre-defined text messages
+using {@link android.support.v4.app.RemoteInput}.
+</p>
+
+<p>When users reply with voice or select one of the available
+messages, the system attaches the text response to the {@link android.content.Intent} you specified
+for the notification action and sends that intent to your handheld app.</p>
+
+<img src="{@docRoot}wear/images/13_voicereply.png" height="200" width="169"
+style="float:right;margin:0 0 20px 40px;clear:right" />
+<img src="{@docRoot}wear/images/03_actions.png" height="200" width="169"
+style="float:right;margin:0 0 20px 40px" />
+
+<p class="note"><strong>Note:</strong> The Android emulator does not support voice input. When
+using the emulator for a wearable device, enable <b>Hardware keyboard present</b> in the AVD settings
+so you can type replies instead.</p>
+
+<h2 id="VoiceInput">Define the Voice Input</h2>
+
+<p>To create an action that supports voice input, create an instance of 
+  {@link android.support.v4.app.RemoteInput.Builder} that you can add to your notification action.
+  This class's constructor accepts a string that the system uses as
+  the key for the voice input, which you'll later use to retrieve the text of the
+  input in your handheld app.</p>
+
+<p>For example, here's how to create a
+{@link android.support.v4.app.RemoteInput} object that provides a custom
+label for the voice input prompt:</p>
+
+<pre class="prettyprint">
+// Key for the string that's delivered in the action's intent
+private static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
+
+String replyLabel = getResources().getString(R.string.reply_label);
+
+RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
+        .setLabel(replyLabel)
+        .build();
+</pre>
+
+
+<h3>Add Pre-defined Text Responses</h3>
+
+<img src="{@docRoot}wear/images/12_voicereply.png" height="200"
+style="float:right;margin:0 0 20px 40px" />
+
+<p>In addition to allowing voice input, you can
+    provide up to five text responses that the user can select for quick replies. Call
+ {@link android.support.v4.app.RemoteInput.Builder#setChoices setChoices()} and pass it a string array.</p>
+
+<p>For example, you can define some responses in a resource array:</p>
+
+<p class="code-caption">res/values/strings.xml</code>
+<pre class="prettyprint">
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;string-array name="reply_choices">
+        &lt;item>Yes&lt;/item>
+        &lt;item>No&lt;/item>
+        &lt;item>Maybe&lt;/item>
+    &lt;/string-array>
+&lt;/resources>
+</pre>
+
+<p>Then, inflate the string array and add it to the
+ {@link android.support.v4.app.RemoteInput}:</p>
+
+<pre>
+public static final EXTRA_VOICE_REPLY = "extra_voice_reply";
+...
+String replyLabel = getResources().getString(R.string.reply_label);
+String[] replyChoices = getResources().getStringArray(R.array.reply_choices);
+
+RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
+        .setLabel(replyLabel)
+        .setChoices(replyChoices)
+        .build();
+</pre>
+
+<h2 id="AddAction">Add the Voice Input as a Notification Action</h2>
+
+<p>
+To set the voice input, attach your
+{@link android.support.v4.app.RemoteInput} object to an action using
+{@link android.support.v4.app.NotificationCompat.Action.Builder#addRemoteInput addRemoteInput()}.
+You can then apply the action to the notification. For example:
+</p>
+
+<pre>
+// Create an intent for the reply action
+Intent replyIntent = new Intent(this, ReplyActivity.class);
+PendingIntent replyPendingIntent =
+        PendingIntent.getActivity(this, 0, replyIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+// Create the reply action and add the remote input
+NotificationCompat.Action action =
+        new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon,
+                getString(R.string.label, replyPendingIntent))
+                .addRemoteInput(remoteInput)
+                .build();
+
+// Build the notification and add the action via WearableExtender
+Notification notification =
+        new NotificationCompat.Builder(mContext)
+                .setSmallIcon(R.drawable.ic_message)
+                .setContentTitle(getString(R.string.title))
+                .setContentText(getString(R.string.content))
+                .extend(new WearableExtender().addAction(action))
+                .build();
+
+// Issue the notification
+NotificationManagerCompat notificationManager =
+        NotificationManagerCompat.from(mContext);
+notificationManager.notify(notificationId, notification);
+</pre>
+<p>
+When you issue this notification, users can swipe to the left to see the "Reply" action button.
+</p>
+
+<h2 id="ReceiveInput">Receiving the Voice Input as a String</h2>
+<p>
+To receive the user's transcribed message in the activity you declared in the reply action's intent,
+call {@link android.support.v4.app.RemoteInput#getResultsFromIntent getResultsFromIntent()},
+passing in the "Reply" action's intent. This method returns a {@link android.os.Bundle} that
+contains the text response. You can then query the {@link android.os.Bundle} to obtain the response.
+
+<p class="note"><b>Note:</b> Do not use {@link android.content.Intent#getExtras Intent.getExtras()}
+to obtain the voice result, because the voice input is stored as
+{@link android.content.ClipData}. The {@link android.support.v4.app.RemoteInput#getResultsFromIntent
+getResultsFromIntent()} method provides a convenient way to receive a character sequence without
+having to process the {@link android.content.ClipData} yourself.
+</p>
+
+<p>
+The following code shows a method that accepts an intent and returns the voice response,
+which is referenced by the <code>EXTRA_VOICE_REPLY</code> key that is used in the previous examples:
+</p>
+
+<pre>
+/**
+ * Obtain the intent that started this activity by calling
+ * Activity.getIntent() and pass it into this method to
+ * get the associated voice input string.
+ */
+
+private CharSequence getMessageText(Intent intent) {
+    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+        if (remoteInput != null) {
+            return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
+        }
+    }
+    return null;
+}
+</pre>
\ No newline at end of file
diff --git a/docs/html/tv/images/atv-framed.png b/docs/html/tv/images/atv-framed.png
new file mode 100644
index 0000000..4cedab2
--- /dev/null
+++ b/docs/html/tv/images/atv-framed.png
Binary files differ
diff --git a/docs/html/tv/images/components.png b/docs/html/tv/images/components.png
new file mode 100644
index 0000000..5984c64
--- /dev/null
+++ b/docs/html/tv/images/components.png
Binary files differ
diff --git a/docs/html/tv/images/hero.jpg b/docs/html/tv/images/hero.jpg
new file mode 100644
index 0000000..e9f8c47
--- /dev/null
+++ b/docs/html/tv/images/hero.jpg
Binary files differ
diff --git a/docs/html/tv/images/recommendations.png b/docs/html/tv/images/recommendations.png
new file mode 100644
index 0000000..e7aa01d
--- /dev/null
+++ b/docs/html/tv/images/recommendations.png
Binary files differ
diff --git a/docs/html/tv/images/search.png b/docs/html/tv/images/search.png
new file mode 100644
index 0000000..92e1e5f
--- /dev/null
+++ b/docs/html/tv/images/search.png
Binary files differ
diff --git a/docs/html/tv/index.jd b/docs/html/tv/index.jd
new file mode 100644
index 0000000..ff70977
--- /dev/null
+++ b/docs/html/tv/index.jd
@@ -0,0 +1,229 @@
+page.title=Android TV
+page.viewport_width=970
+fullpage=true
+no_footer_links=true
+page.type=about
+
+@jd:body
+
+<style>
+.fullpage>#footer,
+#jd-content>.content-footer.wrap {
+  display:none;
+}
+</style>
+
+
+<div class="landing-body-content">
+
+  <div class="landing-hero-container">
+
+    <div class="landing-section tv-hero">
+      <div class="landing-hero-scrim"></div>
+      <div class="landing-hero-wrap">
+        <div class="vertical-center-outer">
+          <div class="vertical-center-inner">
+
+            <div class="col-10">
+              <div class="landing-section-header">
+                <div class="landing-h1 hero">Android TV</div>
+                <div class="landing-subhead hero">Big screen apps, games, and content</div>
+                <div class="landing-hero-description">
+                  <p>Recommend great content to users right on the home screen.
+                    Enable users to find movies through voice search.
+                    Engage users with fluid, immersive games.</p>
+                </div>
+              </div>
+
+              <div class="landing-body">
+                <a href="{@docRoot}preview/tv/start/index.html" class="landing-button
+                  landing-primary" style="margin-top: 40px;">
+                  Get Started
+                </a>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div> <!-- end .wrap -->
+      <div class="landing-scroll-down-affordance">
+        <a class="landing-down-arrow" href="#reimagine-your-app">
+          <img src="{@docRoot}wear/images/carrot.png" alt="Scroll down to read more">
+        </a>
+      </div>
+    </div> <!-- end .landing-section .landing-hero -->
+
+    <div class="landing-rest-of-page">
+
+      <div class="landing-section" style="background-color:#f5f5f5" id="reimagine-your-app">
+        <div class="wrap">
+
+          <div class="landing-section-header">
+            <div class="landing-h1">Reimagine Your App</div>
+            <div class="landing-subhead">
+              Design your app to shine on the biggest screen in the house.
+            </div>
+          </div>
+
+          <div class="landing-body landing-align-center">
+            <img src="{@docRoot}tv/images/atv-framed.png" alt="Android TV" >
+            <p>Simple. Cinematic. Beautiful.</p>
+            <p class="landing-small">
+              Smooth, fast interactions are key to a successful TV app. Keep navigation simple
+              and light. <br>Bring your content forward to let users enjoy it with a minimum of
+              effort.
+            </p>
+            <p class="landing-small">
+              <a href="{@docRoot}preview/tv/design/index.html">Learn about design for TV</a>
+            </p>
+          </div>
+
+        </div>  <!-- end .wrap -->
+      </div>  <!-- end .landing-section -->
+
+      <div class="landing-section landing-gray-background">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1">Build to Entertain</div>
+            <div class="landing-subhead">
+              Android TV lets you engage your users in a new, shared environment.<br>
+              Find out how to get your app ready for its big-screen debut.
+            </div>
+          </div>
+
+
+          <div class="landing-body">
+            <div class="landing-breakout cols">
+
+              <div class="col-3-wide">
+                <img src="{@docRoot}tv/images/components.png" alt="TV layout components"
+                  style="margin-left: 23px;">
+
+                <p>Made for TV</p>
+                <p class="landing-small">
+                  Take advantage of pre-built fragments for browsing and interacting with media
+                  catalogs.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}preview/tv/ui/browse.html">Learn pre-built fragments</a>
+                </p>
+              </div>
+
+              <div class="col-3-wide">
+                <img src="{@docRoot}tv/images/search.png" alt="Search"
+                  style="margin-left: 23px;">
+
+                <p>Get Found</p>
+                <p class="landing-small">
+                  Help users find your content quickly with in-app searching.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}preview/tv/ui/in-app-search.html">Learn about app search</a>
+                </p>
+              </div>
+
+              <div class="col-3-wide">
+                <img src="{@docRoot}tv/images/recommendations.png" alt="Recommendations"
+                  style="margin-left: 23px;">
+
+                <p>Recommend</p>
+                <p class="landing-small">
+                  Suggest content from your app to keep your users coming back.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}preview/tv/ui/recommendations.html">Learn about
+                    recommendations</a>
+                </p>
+              </div>
+
+            </div>
+
+          </div>
+        </div>  <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+      <div class="landing-section landing-red-background" id="start">
+        <div class="wrap">
+          <div class="landing-section-header">
+            <div class="landing-h1 landing-align-left">Get Started with Android TV</div>
+            <div class="landing-body">
+              <p>Begin building TV apps right away using these developer resources:</p>
+            </div>
+          </div>
+
+          <div class="landing-body">
+            <div class="landing-breakout cols">
+              <div class="col-8" style="margin-left: -8px;">
+                <p style="font-size: 24px;">L Developer Preview</p>
+                <p>
+                   Get all the tools you need to build and test TV apps. Download the preview and
+                   start creating your big-screen experience.
+                </p>
+
+              </div>
+
+              <div class="col-8">
+                <p style="font-size: 24px;">ADT-1 Developer Kit</p>
+                <p>
+                  Request an <a href="{@docRoot}preview/tv/adt-1/index.html"
+                  style="color: white;"><u>ADT-1 Developer Kit</u></a>, a compact and powerful
+                  streaming media player and gamepad, ideal for developing and testing apps for TV.
+                </p>
+
+              </div>
+
+            </div>
+          </div>
+
+          <div class="landing-body">
+            <div class="landing-breakout cols">
+
+              <div class="col-8">
+                <a href="{@docRoot}preview/tv/start/index.html" class="landing-button landing-primary">
+                  Get Started
+                </a>
+              </div>
+
+              <div class="col-8">
+                <a href="https://support.google.com/googleplay/android-developer/contact/adt_request"
+                  class="landing-button landing-primary">
+                  Request the ADT-1 Developer Kit
+                </a>
+              </div>
+            </div>
+          </div>
+
+        </div>  <!-- end .wrap -->
+      </div> <!-- end .landing-section -->
+
+    </div> <!-- end .landing-rest-of-page -->
+
+      <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement"
+        style="border-top: none;">
+        <div class="layout-content-col col-16" style="padding-top:4px">
+          <style>#___plusone_0 {float:right !important;}</style>
+          <div class="g-plusone" data-size="medium"></div>
+        </div>
+      </div>
+      <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1">
+        <div id="copyright">
+          Except as noted, this content is
+          licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+          Creative Commons Attribution 2.5</a>. For details and
+          restrictions, see the <a href="/license.html">Content
+          License</a>.
+        </div>
+      </div>
+
+  </div> <!-- end .landing-hero-container -->
+
+  <script>
+  $("a.landing-down-arrow").on("click", function(e) {
+    $("body").animate({
+      scrollTop: $(".tv-hero").height() + 120
+    }, 1000, "easeOutQuint");
+    e.preventDefault();
+  });
+  </script>
+
+</div> <!-- end landing-body-content -->
+
diff --git a/docs/html/wear/css/wear.css b/docs/html/wear/css/wear.css
deleted file mode 100644
index fe9eef2..0000000
--- a/docs/html/wear/css/wear.css
+++ /dev/null
@@ -1,447 +0,0 @@
-/**
- * UTILITIES
- */
-
-
-.border-box {
-  box-sizing: border-box;
-}
-
-.vertical-center-outer {
-  display: table;
-  height: 100%;
-  width: 100%;
-}
-
-.vertical-center-inner {
-  display: table-cell;
-  vertical-align: middle;
-}
-
-/**
- * TYPE STYLES
- */
-
-.wear-h1 {
-  font-weight: 300;
-  font-size: 60px;
-  line-height: 78px;
-  text-align: center;
-  letter-spacing: -1px;
-}
-
-.wear-pre-h1 {
-  font-weight: 400;
-  font-size: 28px;
-  color: #93B73F;
-  line-height: 36px;
-  text-align: center;
-  letter-spacing: -1px;
-  text-transform: uppercase;
-
-}
-
-.wear-h1.hero {
-  text-align: left;
-}
-
-.wear-h2 {
-  font-weight: 300;
-  font-size: 42px;
-  line-height: 64px;
-  text-align: center;
-}
-
-
-.wear-subhead {
-  color: #999999;
-  font-size: 20px;
-  line-height: 28px;
-  text-align: center;
-}
-.wear-subhead.hero {
-  text-align: left;
-  color: white;
-}
-
-.wear-hero-description {
-  text-align: left;
-  margin: 1em 0;
-}
-
-.wear-hero-description p {
-  font-weight: 300;
-  margin: 0;
-  font-size: 18px;
-  line-height: 24px;
-}
-
-.wear-body .wear-small {
-  font-size: 14px;
-  line-height: 19px;
-}
-
-.wear-body.wear-align-center {
-  text-align: center;
-}
-
-.wear-align-left {
-  text-align: left;
-}
-
-/**
- * LAYOUT
- */
-
-#body-content,
-.fullpage,
-#jd-content,
-.jd-descr,
-.wear-body-content {
-  height: 100%;
-}
-
-.wear-section {
-  padding: 80px 10px 80px;
-  width: 100%;
-  margin-left: -10px;
-  text-rendering: optimizeLegibility;
-}
-
-#extending-android-to-wearables {
-  padding-top: 30px;
-}
-
-.wear-short-section {
-  padding: 40px 10px 28px;
-}
-
-.wear-gray-background {
-  background-color: #e9e9e9;
-}
-
-.wear-white-background {
-  background-color: white;
-}
-
-.wear-red-background {
-  color: white;
-  background-color: hsl(8, 70%, 54%);
-}
-
-.wear-subhead-red {
-  color: hsl(8, 71%, 84%);
-  text-align: left;
-}
-
-.wear-subhead-red p {
-  margin-top: 20px;
-}
-
-.wear-hero-container {
-  height: 100%;
-}
-
-.wear-hero {
-  height: calc(100% - 70px);
-  min-height: 504px;
-  margin-top: 0;
-  padding-top: 0;
-  padding-bottom: 0;
-  background-image: url(/wear/images/hero.jpg);
-  background-size: cover;
-  background-position: right center;
-  color: white;
-  position: relative;
-  overflow: hidden;
-}
-
-.wear-hero-scrim {
-  background: black;
-  opacity: .2;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  margin-left: -10px;
-}
-
-.wear-hero-wrap {
-  margin: 0 auto;
-  width: 940px;
-  clear: both;
-  height: 100%;
-  position: relative;
-}
-
-.wear-section-header {
-  margin-bottom: 40px;
-}
-
-.wear-hero-wrap .wear-section-header {
-  margin-bottom: 16px;
-}
-
-.wear-body {
-  font-size: 18px;
-  line-height: 24px;
-}
-
-.wear-button {
-  white-space: nowrap;
-  display: inline-block;
-  padding: 16px 32px;
-  font-size: 18px;
-  font-weight: 500;
-  line-height: 24px;
-  cursor: pointer;
-  color: white;
-  -webkit-user-select: none;
-     -moz-user-select: none;
-       -o-user-select: none;
-  user-select: none;
-  -webkit-transition: .2s background-color ease-in-out;
-     -moz-transition: .2s background-color ease-in-out;
-       -o-transition: .2s background-color ease-in-out;
-  transition: .2s background-color ease-in-out;
-}
-
-.wear-primary {
-  background-color: hsl(8, 70%, 54%); /* #dc4b35 */
-  color: #f8f8f8;
-}
-
-.wear-button.wear-primary:hover {
-  background-color: hsl(8, 70%, 44%); /* #bf3722 */
-}
-
-.wear-button.wear-primary:active {
-  background-color: hsl(8, 70%, 36%); /* # */
-}
-
-.wear-button.wear-secondary {
-  background-color: hsl(8, 70%, 44%);
-}
-
-.wear-button.wear-secondary:hover {
-  background-color: hsl(8, 70%, 36%);
-}
-
-.wear-button.wear-secondary:active {
-  background-color: hsl(8, 70%, 30%);
-}
-
-a.wear-button,
-a.wear-button:hover,
-a.wear-button:visited {
-  color: white !important;
-}
-
-.wear-video-link {
-  white-space: nowrap;
-  display: inline-block;
-  padding: 16px 32px 16px 82px;
-  font-size: 18px;
-  font-weight: 400;
-  line-height: 24px;
-  cursor: pointer;
-  color: hsla(0, 0%, 100%, .8);
-  -webkit-user-select: none;
-     -moz-user-select: none;
-       -o-user-select: none;
-  user-select: none;
-  -webkit-transition: .2s color ease-in-out;
-     -moz-transition: .2s color ease-in-out;
-       -o-transition: .2s color ease-in-out;
-  transition: .2s color ease-in-out;
-}
-
-.wear-video-link:before {
-  height: 64px;
-  width: 64px;
-  display: inline-block;
-  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAFuklEQVR42u2dXWgcVRSAV9LWtBBTTZVWUhNqEQtq1QeroDRKFRFsROqTYPuo+JCiIoJKFC0USqlUfCiowRcfrBgVUUElefAPkW5T8aeaGn9aRbFsjP0x2cx8PuRMvFxmdjeb2Z17Z8+B85DsZPbO+eaec3/OPSkABdXsVI2gABSAqgJQAKoKQAGoKgAFoKoAFICqAlAAqgpAAai6DqDRAiwDeoFtwB7gPaAInABKwKToCWAMeB/YDdwJrAWWNLh9+QMAXABsBQ4A3wFTwAxQBmaBAAhjNJDPy3L938BXwAvArUCHAkh+kCXAVcA+YBw4bRg7MngtkgTlDPA98CywHmhTAP8/xCbgVeAvMZZpwDQllN7xB/AysKGlAQAXAvuBkzW85UVgCBgENlfQQbmuWAXELPAnsAvoaikAQBtwh/j3coLhS2LIfqCzzu/plL8fkvvFgZiR4L2lHrfkHQBgpQTFUwmGnwC212v0KjC2y/3jQPwDPA+05xYAcBHwubx1YZzhC02QBBBRbxgBzssdAOBy4JgRZE0ZTPuNr7FHDCbEhqNAd24AAN0yUbID7QSwsZChABut3hANXY8Bq70HIMb/Ocb4w81+66v0hmGrN0QQ1ngLQJYRvpWHMWWo4KDIaMnuCcVKgdlZAGL8t2J8vpPGrwChDLyWBMFlAA8D0z4ZvwKEs8D93gCQEc9Jy/jFgkdizaRDGUSs8wXAu1bQLQE9ngHosWbPAXDQeQAypT9rBd3+gociyxi2K9riLABZUj5iuZ6RgsciM2OzFxw2A7JrAO6VwGtKTwpG+Anoy9AVmb3gDHCPcwCAFcChRox6jPu9CazMeFQUAKNRL3AJwE2yopjq228BQPZ/d2bcCyaBTa4BeNGa8Q6naIA4GQWubiKEYWvBbp8zAGQt5VfL/fQ3GEAkTzXDLVkjokA2k5a7AuA2GaLNj/tTfvhq0pQgbcwLQtlQusYVALtlzSR191MjADNI9zbJDZWBR10BMGr5/4GMADQ0SAMDlht62xUAxy0AmzMEEMnhtIO0ZF2YAH5wITd0hQw/5wE04M1bjDyXZpC2hqMlYGnWAHqBf40APOEYgChI35VSWyasWfGqrAH0WVkOIw4CSC1IG2tDoSy7XJE1gPs8ArDoIG0BmJGk30wBDHgGYFFBOgbAtqwB7GxxAHerC8rOBU0Dt2gQzjYIb8gawDor+6HVhqFdrkzEwhabiAVOTMSkUb+06FLEUVfWgj5q0cW4g64AeNo66ZLlcnTDNmesBN4y8KArAG6QU42ttCEzBVzpCoAO4EfLDeV5SzIEvgHaXdqUP2BlQud1Ux55zj2uZUX02cPRnKalRLmu17qYmPWF5YbymJgVAh8Ay5wCII3ZEZOYm6fURGT2u9X43Mnk3CDHybmfmRVYXExPv9nKEcpLejqSC3SjdY2TBzTesHqB7wc0onTEV2KucxLApXKkJy9HlAI5anuJFwCkYQ/EuCJfD+mdBnYkXOssgHY53un7MdVZ4CVgqVcADAhjMafkfTioHc14P04yvvMApIEXy5F/+7S8y6UKolPyR4BVVf7Wi2IdawwIPhTrmAW+rmZ8bwBIQ7vloXwoVzNWS6UUrwAYy9YfOlqwKZDkgneA5Qu4l3cly84F9sqGhislywLmaozuYoGFXr0DII1ukxP1hxJ6QzR7HqLxRfumZaRzXZ3f4XXZyi7gCeB3kqsnzs+kSb9s5XHgMeD8RTxDLgq3rmeuYuFvNYCoR8wqujNi+L3UWBcu9wAMt3QZ8LiMlk5RuU50teq6kcEDgTolveIRYHUQBOek1O5cFu/ukLz7/ZJgNSm+OirebWpgaPS7slxfAr4EngGuX8jopqUBxGzyrAVuB54EXgc+lV4yLhO8cfn5E+ZqUD8kBu9sQvv0Hzj4rmoEBaAAVBWAAlBVAApAVQEoAFUFoABUFYACUFUACkC1CfofXVRJocowZVYAAAAASUVORK5CYII=);
-  background-size: contain;
-  position: absolute;
-  content: "";
-  opacity: .7;
-  margin-top: -19px;
-  margin-left: -64px;
-  -webkit-transition: .2s opacity ease-in-out;
-     -moz-transition: .2s opacity ease-in-out;
-       -o-transition: .2s opacity ease-in-out;
-  transition: .2s opacity ease-in-out;
-}
-
-.wear-video-link:hover {
-  color: hsla(0, 0%, 100%, 1);
-}
-
-.wear-video-link:hover:before {
-  opacity: 1;
-}
-
-.wear-social-image {
-  float: left;
-  margin-right: 14px;
-  height: 64px;
-  width: 64px;
-}
-
-.wear-social-copy {
-  padding-left: 78px;
-}
-
-.wear-scroll-down-affordance {
-  position: absolute;
-  bottom: 0;
-  width: 100%;
-  text-align: center;
-  z-index: 10;
-}
-
-.wear-down-arrow {
-  padding: 24px;
-  display: inline-block;
-  opacity: .5;
-  -webkit-transition: .2s opacity ease-in-out;
-     -moz-transition: .2s opacity ease-in-out;
-       -o-transition: .2s opacity ease-in-out;
-  transition: .2s opacity ease-in-out;
-
-  -webkit-animation-name: pulse-opacity;
-  -webkit-animation-duration: 4s;
-}
-
-.wear-down-arrow:hover {
-  opacity: 1;
-}
-
-.wear-down-arrow img {
-  height: 28px;
-  width: 28px;
-  margin: 0 auto;
-  display: block;
-}
-
-.wear-divider {
-  display: inline-block;
-  height: 2px;
-  background-color: white;
-  position: relative;
-  margin: 10px 0;
-}
-
-/* 3 CLOLUMN LAYOUT */
-
-.wear-breakout {
-  margin-top: 40px;
-  margin-bottom: 40px;
-}
-
-.wear-breakout img {
-  margin-bottom: 20px;
-}
-
-.wear-partners img {
-  margin-bottom: 20px;
-}
-
-.wear-breakout p {
-  padding: 0 23px;
-}
-
-.wear-inset-video-container {
-  position: relative;
-}
-
-.wear-inset-video-container img.gif {
-  max-width: 222px;
-  position: absolute;
-  top: 40px;
-  left: 40px;
-}
-
-img.wear-bezel-only {
-  height:302px;
-  width:302px;
-}
-
-.wear-breakout.wear-partners img {
-  margin-bottom: 20px;
-}
-
-.col-3-wide {
-  display: inline;
-  float: left;
-  margin-left: 10px;
-  margin-right: 10px;
-}
-
-.col-3-wide {
-  width: 302px;
-}
-
-/**
- * ANIMATION
- */
-
-@-webkit-keyframes pulse-opacity {
-  0% {
-    opacity: .5;
-  }
-  20% {
-    opacity: .5;
-  }
-  40% {
-    opacity: 1;
-  }
-  60% {
-    opacity: .5;
-  }
-  80% {
-    opacity: 1;
-  }
-  100% {
-    opacity: .5;
-  }
-}
-
-
-
-/**
- * VIDEO
- */
-
-#video-container {
-  display:none;
-  position:fixed;
-  top:0;
-  left:-10px; 
-  width:102%;
-  height:100%;
-  background-color:rgba(0,0,0,0.7);
-  z-index:99;
-}
-
-#video-frame {
-  width:940px;
-  height:526.4px;
-  margin:80px auto 0;
-  display:none;
-}
-
-.video-close {
-cursor: pointer;
-position: relative;
-left: 940px;
-top: 0;
-pointer-events: all;
-}
-
-#icon-video-close {
-background-image: url("../images/close.png");
-background-position: 0 0;
-height: 36px;
-width: 36px;
-display:block;
-}
diff --git a/docs/html/wear/images/features/apps.png b/docs/html/wear/images/features/apps.png
new file mode 100644
index 0000000..dbbb5e5
--- /dev/null
+++ b/docs/html/wear/images/features/apps.png
Binary files differ
diff --git a/docs/html/wear/images/features/notifications.png b/docs/html/wear/images/features/notifications.png
new file mode 100644
index 0000000..128853c
--- /dev/null
+++ b/docs/html/wear/images/features/notifications.png
Binary files differ
diff --git a/docs/html/wear/images/features/s1.png b/docs/html/wear/images/features/s1.png
deleted file mode 100644
index ba96cf8..0000000
--- a/docs/html/wear/images/features/s1.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s2.png b/docs/html/wear/images/features/s2.png
deleted file mode 100644
index af28496..0000000
--- a/docs/html/wear/images/features/s2.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s3.png b/docs/html/wear/images/features/s3.png
deleted file mode 100644
index 6ae9868..0000000
--- a/docs/html/wear/images/features/s3.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s4.png b/docs/html/wear/images/features/s4.png
deleted file mode 100644
index 125713d..0000000
--- a/docs/html/wear/images/features/s4.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/send-data.png b/docs/html/wear/images/features/send-data.png
new file mode 100644
index 0000000..7010e3f
--- /dev/null
+++ b/docs/html/wear/images/features/send-data.png
Binary files differ
diff --git a/docs/html/wear/images/features/ts1.png b/docs/html/wear/images/features/ts1.png
deleted file mode 100644
index 5d4b1c1..0000000
--- a/docs/html/wear/images/features/ts1.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts2.png b/docs/html/wear/images/features/ts2.png
deleted file mode 100644
index dc798c5..0000000
--- a/docs/html/wear/images/features/ts2.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts3.png b/docs/html/wear/images/features/ts3.png
deleted file mode 100644
index 0d68ebc..0000000
--- a/docs/html/wear/images/features/ts3.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts4.png b/docs/html/wear/images/features/voice.png
similarity index 100%
rename from docs/html/wear/images/features/ts4.png
rename to docs/html/wear/images/features/voice.png
Binary files differ
diff --git a/docs/html/wear/images/hero.jpg b/docs/html/wear/images/hero.jpg
index 40cc03c..0b4debb 100644
--- a/docs/html/wear/images/hero.jpg
+++ b/docs/html/wear/images/hero.jpg
Binary files differ
diff --git a/docs/html/wear/images/kitchen_still.jpg b/docs/html/wear/images/kitchen_still.jpg
deleted file mode 100644
index 4afe359..0000000
--- a/docs/html/wear/images/kitchen_still.jpg
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/notifications.png b/docs/html/wear/images/notifications.png
new file mode 100644
index 0000000..8b1cb0b
--- /dev/null
+++ b/docs/html/wear/images/notifications.png
Binary files differ
diff --git a/docs/html/wear/images/steps.png b/docs/html/wear/images/steps.png
new file mode 100644
index 0000000..965f382
--- /dev/null
+++ b/docs/html/wear/images/steps.png
Binary files differ
diff --git a/docs/html/wear/images/voicecommands.png b/docs/html/wear/images/voicecommands.png
new file mode 100644
index 0000000..9839ed8
--- /dev/null
+++ b/docs/html/wear/images/voicecommands.png
Binary files differ
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index 659e9f2..5dd7690 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -2,7 +2,7 @@
 page.viewport_width=970
 fullpage=true
 no_footer_links=true
-page.customHeadTag=<link rel="stylesheet" type="text/css" href="/wear/css/wear.css">
+page.type=about
 
 @jd:body
 
@@ -14,8 +14,6 @@
 </style>
 
 
-
-
 <div id="video-container">
   <div id="video-frame">
     <div class="video-close">
@@ -23,28 +21,28 @@
     </div>
     <script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
     <div id="ytapiplayer">
-      <a href="http://www.youtube.com/watch?v=0xQ3y902DEQ"><img width=940
-      src="https://i1.ytimg.com/vi/0xQ3y902DEQ/maxresdefault.jpg"></a><!--You need Flash player 8+ and JavaScript enabled to view this video. -->
+      <a href="http://www.youtube.com/watch?v=0xQ3y902DEQ"><img width="940" src="https://i1.ytimg.com/vi/0xQ3y902DEQ/maxresdefault.jpg"></a><!--You need Flash player 8+ and JavaScript enabled to view this video. -->
     </div>
   </div>
 </div>
 
 
 
-<div class="wear-body-content">
-  <div class="wear-hero-container">
-    <div class="wear-section wear-hero">
-      <div class="wear-hero-scrim"></div>
-      <div class="wear-hero-wrap">
+<div class="landing-body-content">
+  <div class="landing-hero-container">
+    <div class="landing-section wear-hero">
+      <div class="landing-hero-scrim"></div>
+      <div class="landing-hero-wrap">
         <div class="vertical-center-outer">
           <div class="vertical-center-inner">
 
             <div class="col-10">
-              <div class="wear-section-header">
-                <div class="wear-h1 hero">Android Wear</div>
-                <div class="wear-subhead hero">Information that moves with you</div>
+              <div class="landing-section-header">
+                <div class="landing-h1 hero">Android Wear</div>
+                <div class="landing-subhead hero">Information that moves with you</div>
               </div>
-              <div class="wear-hero-description">
+              <div class="landing-hero-description">
+
                 <p>Small, powerful devices, worn on the body.
                 Useful information when you need it most.
                 Intelligent answers to spoken questions.
@@ -52,222 +50,151 @@
                 Your key to a multiscreen world.</p>
               </div>
 
-              <div class="wear-body">
-                <a href="/wear/preview/start.html" class="wear-button wear-primary" style="margin-top: 40px;">
-                  Get the Developer Preview
+              <div class="landing-body">
+                <a href="{@docRoot}training/building-wearables.html" class="landing-button landing-primary" style="margin-top: 40px;">
+                  Get Started
                 </a>
-                <a id="watchVideo" href="https://youtube.googleapis.com/v/0xQ3y902DEQ">
-                  <div class="wear-video-link">Watch the video</div>
-                </a>
-<script>
-$("#watchVideo").on("click", function(e) {
-  $("#video-container").fadeIn(400, function(){$("#video-frame").show()});
-
-  var params = { allowScriptAccess: "always"};
-  var atts = { id: "ytapiplayer" };
-  swfobject.embedSWF("//www.youtube.com/v/0xQ3y902DEQ?enablejsapi=1&playerapiid=ytplayer&version=3&HD=1;rel=0;showinfo=0;modestbranding;origin=developer.android.com;autohide=1;autoplay=1",
-    "ytapiplayer", "940", "526.4", "8", null, null, params, atts);
-
-  e.preventDefault();
-});
-$("#icon-video-close").on("click", function() {
-  ytplayer = document.getElementById("ytapiplayer");
-  try {
-    ytplayer.stopVideo();
-    $(ytplayer).hide();
-    $("#video-container").fadeOut(400);
-  } catch(e) {
-    console.log('Video not available');
-    $("#video-container").fadeOut(400);
-  }
-});
-</script>
               </div>
             </div>
 
           </div>
         </div>
       </div> <!-- end .wrap -->
-      <div class="wear-scroll-down-affordance">
-        <a class="wear-down-arrow" href="#extending-android-to-wearables">
-          <img src="/wear/images/carrot.png" alt="Scroll down to read more">
+      <div class="landing-scroll-down-affordance">
+        <a class="landing-down-arrow" href="#extending-android-to-wearables">
+          <img src="{@docRoot}wear/images/carrot.png" alt="Scroll down to read more">
         </a>
       </div>
-    </div> <!-- end .wear-section .wear-hero -->
-  </div> <!-- end .wear-hero-container -->
+    </div> <!-- end .landing-section .landing-hero -->
 
-    <div class="wear-rest-of-page">
-      <div class="wear-section" id="extending-android-to-wearables">
+
+    <div class="landing-rest-of-page" style="margin-top:80px">
+      <div class="landing-section" id="extending-android-to-wearables">
         <div class="wrap">
-          <div class="wear-section-header">
-            <div class="wear-h1">Extending Android to Wearables</div>
-            <div class="wear-subhead">
-              Android Wear extends the Android platform to a new generation of wearable devices. <br>
-              The user experience is designed specifically for wearables.
+          <div class="landing-section-header">
+            <div class="landing-h1">Extending Android to Wearables</div>
+            <div class="landing-subhead">
+              Android Wear extends the Android platform to a new generation of devices, <br>
+              with a user experience that's designed specifically for wearables.
+
             </div>
           </div>
 
-          <div class="wear-body">
-            <div class="wear-breakout cols">
+          <div class="landing-body">
+            <div class="landing-breakout cols">
               <div class="col-3-wide">
-
-                <div class="wear-inset-video-container">
-                  <img class="wear-bezel-only" src="/wear/images/screens/bezel.png" alt="">
-                  <img class="gif" src="/wear/images/screens/reservation_animated.gif">
+              <div class="framed-wear-square">
+                <img src="{@docRoot}wear/images/notifications.png" itemprop="image" alt="">
                 </div>
-
-                <p class="wear-small">
-                  Say “Ok Google” to ask questions and get stuff done.
+                <br />
+                <p class="landing-small">
+                  Get glanceable, actionable information at just the right time with notifications
+                  that are synced from your handheld device.
                 </p>
               </div>
               <div class="col-3-wide">
-                <img src="/wear/images/screens/circle_message2.png" itemprop="image" alt="" >
-                <p class="wear-small">
-                  Get glanceable, actionable information at just the right time throughout the day.
-                </p>
-              </div>
-              <div class="col-3-wide">
-                <img src="/wear/images/screens/fitness-24.png" alt="">
-                <p class="wear-small">
-                  A wide range of sensors is available to your applications, from accelerometers to heart rate monitors.
-                </p>
-              </div>
-            </div>
-
-            <p>
-              The Android Wear Developer Preview lets you create wearable experiences for your existing Android apps and see how they will appear on square and round Android wearables.
-            </p>
-
-            <p>
-              Later this year, we’ll be launching the Android Wear SDK, enabling even more customized experiences.
-            </p>
-          </div>
-        </div>  <!-- end .wrap -->
-      </div>  <!-- end .wear-section -->
-
-      <div class="wear-section wear-gray-background">
-        <div class="wrap">
-          <div class="wear-section-header">
-            <div class="wear-h1">Developer Preview</div>
-            <div class="wear-subhead">
-              Your app’s notifications will already appear on Android wearables. <br>
-              With the new Android Wear APIs you can customize and extend those notifications.
-            </div>
-          </div>
-
-
-          <div class="wear-body">
-            <div class="wear-breakout cols">
-              <div class="col-3-wide">
-                <img src="images/screens/14_circle_voicereply.png" alt="">
-                <p>Receive Voice Replies</p>
-                <p class="wear-small">
-                  Add actions to your notifications to allow users to reply by voice or touch. The system delivers the text to your app on the phone.
-                </p>
-                <p class="wear-small">
-                  <a href="/wear/notifications/remote-input.html">Learn about input actions</a>
-                </p>
-              </div>
-              <div class="col-3-wide">
-
-
-                <div class="wear-inset-video-container">
-                  <img class="wear-bezel-only" src="/wear/images/screens/bezel.png" alt="">
-                  <img class="gif" src="/wear/images/screens/pages_animated.gif">
+                <div class="framed-wear-square">
+                  <img src="{@docRoot}wear/images/voicecommands.png">
                 </div>
-
-                <p>Add Notification Pages</p>
-                <p class="wear-small">
-                  Add additional pages to your notification that are visible on the wearable device to provide detailed information on the wrist.
-                </p>
-                <p class="wear-small">
-                  <a href="/wear/notifications/pages.html">Learn about pages</a>
+                <br />
+                <p class="landing-small">
+                  Say “Ok Google” to send messages, ask questions, and get stuff done.
                 </p>
               </div>
               <div class="col-3-wide">
-                <img src="images/screens/11_stack_B.png" alt="">
-                <p>Stack Multiple Notifications</p>
-                <p class="wear-small">
-                  Your app should consolidate similar notifications. On a wearable, you can stack them together so the details for each are immediately available.
-                </p>
-                <p class="wear-small">
-                  <a href="/wear/notifications/stacks.html">Learn about stacks</a>
+              <div class="framed-wear-square">
+                <img src="{@docRoot}wear/images/steps.png" alt="">
+                </div>
+                <br />
+                <p class="landing-small">
+                  Design apps that can access a wide range of sensors and other hardware
+                   directly on the wearable.
                 </p>
               </div>
             </div>
-
             <p>
-              You can also trigger your notifications contextually using existing Android APIs.  For example, use <a href="{@docRoot}training/location/geofencing.html">geofences</a> to provide glanceable information to your users when they are at home, or use the <a href="{@docRoot}training/location/activity-recognition.html">activity detection APIs</a> to send messages to your users’ wrists while they are bicycling.
-            </p>
-
-            <p>See the <a href="{@docRoot}wear/design/index.html">Android Wear Developer Preview Design Principles</a> for more suggestions on creating great wearable experiences.</p>
+              Before you start building, check out the
+              <a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a>
+              to understand how to create great experiences for this exciting, new form factor.</p>
 
           </div>
         </div>  <!-- end .wrap -->
-      </div>  <!-- end .wear-section -->
+      </div>  <!-- end .landing-section -->
 
-      <div class="wear-section" style="background-color:#f5f5f5">
+      <div class="landing-section landing-gray-background">
         <div class="wrap">
-          <div class="wear-section-header">
-            <div class="wear-pre-h1">Coming soon</div>
-            <div class="wear-h1">The Android Wear SDK</div>
-            <div class="wear-subhead">
-              The Developer Preview is just the beginning for Android Wear.
-            </div>
+          <div class="landing-section-header">
+            <div class="landing-h1">Developing for Android Wear</div>
+            <div class="landing-subhead">
+             The Android Wear APIs are delivered in the Android Support Library and Google Play
+             services. When using these libraries, handheld devices running Android 4.3
+             or later can communicate with wearables.</div>
           </div>
 
-          <div class="wear-body">
-            <p>
-              In the coming months we’ll be launching new APIs and features for Android wearables to create even more unique experiences for the wrist:
-            </p>
-
-            <div class="wear-breakout cols">
+          <div class="landing-body">
+            <div class="landing-breakout cols">
               <div class="col-4">
-                <img src="/wear/images/features/ts1.png" alt="">
-                <p>Build Custom UI</p>
-                <p class="wear-small">
-                  Create custom card layouts and run activities directly on  wearables.
+                <img src="{@docRoot}wear/images/features/notifications.png" alt="">
+                <p>Synced Notifications</p>
+                <p class="landing-small">
+                  Notifications on handhelds can automatically sync to wearables, so design them
+                  with both devices in mind.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}training/wearables/notifications/index.html">Build notifications</a>
                 </p>
               </div>
               <div class="col-4">
-                <img src="/wear/images/features/ts2.png" alt="">
-                <p>Send Data</p>
-                <p class="wear-small">
-                  Send data and actions between a phone and a wearable with data replication APIs and RPCs.
-                </p>
-              </div>
-              <div class="col-4">
-                <img src="/wear/images/features/ts3.png" alt="">
-                <p>Control Sensors</p>
-                <p class="wear-small">
-                  Gather sensor data and display it in real-time on Android wearables.
-                </p>
-              </div>
-              <div class="col-4">
-                <img src="/wear/images/features/ts4.png" alt="">
+                <img src="{@docRoot}wear/images/features/voice.png" alt="">
                 <p>Voice Actions</p>
-                <p class="wear-small">
-                  Register your app to handle voice actions, like "Ok Google, take a note."
+                <p class="landing-small">
+                  Register your app to handle voice actions, like "Ok Google, take a&nbsp;note,"
+                  for a hands-free experience.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}training/wearables/apps/voice.html">Integrate voice actions</a>
+                </p>
+              </div>
+              <div class="col-4">
+                <img src="{@docRoot}wear/images/features/apps.png" alt="">
+                <p>Build Wearable Apps</p>
+                <p class="landing-small">
+                  Create custom experiences with activities, services, sensors, and much
+                  more with the Android SDK.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}training/wearables/apps/index.html">Create wearable apps</a>
+                </p>
+              </div>
+              <div class="col-4">
+                <img src="{@docRoot}wear/images/features/send-data.png" alt="">
+                <p>Send Data</p>
+                <p class="landing-small">
+                  Send data and actions between handhelds and wearables with
+                  data replication APIs and RPCs.
+                </p>
+                <p class="landing-small">
+                  <a href="{@docRoot}training/wearables/data-layer/index.html">Work with the Data Layer</a>
+
                 </p>
               </div>
             </div>
-
           </div>
         </div> <!-- end .wrap -->
-      </div> <!-- end .wear-section -->
+      </div> <!-- end .landing-section -->
 
-      <div class="wear-section wear-white-background">
+      <div class="landing-section landing-white-background">
         <div class="wrap">
-          <div class="wear-section-header">
-            <div class="wear-h2">Building an Ecosystem</div>
-            <div class="wear-body wear-align-center">
-              <p class="wear-small">
-                We’re working with several partners to bring you watches powered by Android Wear later this year!
+          <div class="landing-section-header">
+            <div class="landing-h1">Building an Ecosystem</div>
+            <div class="landing-body landing-align-center">
+              <p class="landing-small">
+                We’re working with partners around the world to build watches powered by Android Wear!
               </p>
             </div>
           </div>
 
-          <div class="wear-partners cols">
+          <div class="landing-partners cols">
             <div class="col-4">
               <img src="/wear/images/partners/asus.png" alt="Asus">
             </div>
@@ -303,67 +230,78 @@
             </div>
           </div>
         </div> <!-- end .wrap -->
-      </div> <!-- end .wear-section -->
+      </div>
 
-      <div class="wear-section wear-red-background">
+
+      <div class="landing-section landing-red-background">
         <div class="wrap">
-          <div class="wear-section-header">
-            <div class="wear-h1 wear-align-left">Start working with Android Wear</div>
-            <div class="wear-subhead wear-subhead-red">
+          <div class="landing-section-header">
+            <div class="landing-h1 landing-align-left">Get Started with Android Wear</div>
+
+            <div class="landing-subhead landing-subhead-red">
               <p>
-                Your app’s notifications will already appear on Android wearables. <br>
-                With the new Android Wear APIs, you can customize and extend those notifications.
-              </p>
-              <p>
-                We’re excited about wearables and the experiences developers can create with them. <br>
-                We can’t wait to see what you do next.</p>
+                Set up your development environment and start working with the APIs.
+                We’re excited about the experiences you'll create and can't
+                wait to see what you do next.</p>
             </div>
           </div>
-          <div class="wear-body">
-            <a href="/wear/preview/start.html" class="wear-button wear-secondary" style="margin-top: 20px;">
-              Get the Developer Preview
+          <div class="landing-body">
+            <a href="{@docRoot}training/building-wearables.html" class="landing-button landing-primary" style="margin-top: 20px;">
+              Get Started
             </a>
           </div>
         </div>
       </div>
 
-      <div class="wear-section">
+      <div class="landing-section">
         <div class="wrap">
           <div class="cols">
-            <div class="wear-body">
+            <div class="landing-body">
               <div class="col-3-wide">
                   <a target="_blank" href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc-kIrPiq098QH9dOle-fLef">
-                    <img class="wear-social-image" src="//www.google.com/images/icons/product/youtube-128.png" alt="">
+                    <img class="landing-social-image" src="//www.google.com/images/icons/product/youtube-128.png" alt="">
                   </a>
-                <div class="wear-social-copy">
+                <div class="landing-social-copy">
                   <p>DevBytes</p>
-                  <p class="wear-small">
-                    Learn how to optimize your app notifications for wearable devices in this <a target="_blank" href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc-kIrPiq098QH9dOle-fLef">DevBytes video</a> using the Android Wear Developer Preview.
-                  </p>
+                    <p class="landing-small">
+                    Learn the basic concepts of Android Wear
+                    development with videos from the product team.
+                    </p>
+                    <br>
+                    <p class="landing-small">
+                      <a target="_blank" href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc-kIrPiq098QH9dOle-fLef">DevBytes videos</a>
+                    </p>
                 </div>
               </div>
               <div class="col-3-wide">
-                <a target="_blank" href="http://android-developers.blogspot.com/2014/03/android-wear-developer-preview.html">
-                  <img class="wear-social-image" src="/wear/images/blogger.png" alt="">
+                <a target="_blank" href="http://android-developers.blogspot.com/2014/03/android-landing-developer-preview.html">
+                  <img class="landing-social-image" src="{@docRoot}wear/images/blogger.png" alt="">
                 </a>
-                <div class="wear-social-copy">
+                <div class="landing-social-copy">
                   <p>Blog Post</p>
-                  <p class="wear-small">
-                    Read more about the Android Wear Developer Preview announcement
-                    at the <a target="_blank" href="http://android-developers.blogspot.com/2014/03/android-wear-developer-preview.html">Android Developers Blog</a>.
+                  <p class="landing-small">
+                    Read more about Android Wear development
+                    on our blog. Just search for "Android Wear".
+
                   </p>
+                  <br>
+                    <p class="landing-small">
+                    <a target="_blank" href="http://android-developers.blogspot.com">Android Developers Blog</a>
+                    </p>
+                  <p></p>
                 </div>
               </div>
               <div class="col-3-wide">
                 <a target="_blank" href="http://g.co/androidweardev">
-                  <img class="wear-social-image" src="//www.google.com/images/icons/product/gplus-128.png" alt="+Android Wear Developers">
+                  <img class="landing-social-image" src="//www.google.com/images/icons/product/gplus-128.png" alt="+Android Wear Developers">
                 </a>
-                <div class="wear-social-copy">
+                <div class="landing-social-copy">
                   <p>G+ Community</p>
-                  <p class="wear-small">
-                    Follow us on Google+ to stay up-to-date on Android Wear development and join the discussion!
+                  <p class="landing-small">
+                    Follow us on Google+ to stay up-to-date with Android Wear development and to join the discussion!
+
                   </p>
-                  <p class="wear-small">
+                  <p class="landing-small">
                     <a target="_blank" href="http://g.co/androidweardev">+Android Wear Developers</a>
                   </p>
                 </div>
@@ -371,10 +309,8 @@
             </div>
           </div>
         </div> <!-- end .wrap -->
-      </div> <!-- end .wear-section -->
-    </div> <!-- end .wear-rest-of-page -->
-
-
+      </div> <!-- end .landing-section -->
+    </div> <!-- end .landing-rest-of-page -->
     <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement">
       <div class="layout-content-col col-16" style="padding-top:4px">
         <style>#___plusone_0 {float:right !important;}</style>
@@ -386,20 +322,20 @@
         Except as noted, this content is
         licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
         Creative Commons Attribution 2.5</a>. For details and
-        restrictions, see the <a href="/license.html">Content
+        restrictions, see the <a href="{@docRoot}license.html">Content
         License</a>.
       </div>
     </div>
-
-
-  </div> <!-- end wear-body-content -->
+  </div> <!-- end .landing-hero-container -->
 
   <script>
-  $("a.wear-down-arrow").on("click", function(e) {
+  $("a.landing-down-arrow").on("click", function(e) {
     $("body").animate({
-      scrollTop: $(".wear-hero").height() + 76
+      scrollTop: $(".wear-hero").height() + 120
     }, 1000, "easeOutQuint");
     e.preventDefault();
   });
   </script>
 
+</div> <!-- end landing-body-content -->
+
diff --git a/docs/html/wear/license.jd b/docs/html/wear/license.jd
deleted file mode 100644
index b07dacf..0000000
--- a/docs/html/wear/license.jd
+++ /dev/null
@@ -1,142 +0,0 @@
-page.title=Developer Preview License Agreement
-
-@jd:body
-
-
-<div class="sdk-terms" style="height:auto;border:0;padding:0;width:700px">
-This is the Android Wear Developer Preview License Agreement.
-
-1. Introduction
-
-1.1 The Android Wear Developer Preview Kit (referred to in this License Agreement as the “Developer Preview” and specifically including the Android system files, packaged APIs, Developer Preview library files, and the Developer Preview companion app, if and when they are made available) is licensed to you subject to the terms of this License Agreement. This License Agreement forms a legally binding contract between you and Google in relation to your use of the Developer Preview.
-
-1.2 "Android Wear" means the Android Wear devices and the Android Wear software stack for use on Android Wear devices.
-
-1.3 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
-
-1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
-
-2. Accepting this License Agreement
-
-2.1 In order to use the Developer Preview, you must first agree to this License Agreement. You may not use the Developer Preview if you do not accept this License Agreement.
-
-2.2 By clicking to accept, you hereby agree to the terms of this License Agreement.
-
-2.3 You may not use the Developer Preview and may not accept the License Agreement if you are a person barred from receiving the Developer Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Developer Preview.
-
-2.4 If you are agreeing to be bound by this License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to this License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Developer Preview on behalf of your employer or other entity.
-
-3. Developer Preview License from Google
-
-3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide, royalty-free, internal-use, non-assignable and non-exclusive license to use the Developer Preview solely to develop applications to run on the Android Wear platform for Android Wear devices.
-
-3.2 You agree that Google or third parties own all legal right, title and interest in and to the Developer Preview, including any Intellectual Property Rights that subsist in the Developer Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
-
-3.3 You may not use the Developer Preview for any purpose not expressly permitted by this License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Developer Preview or any part of the Developer Preview; or (b) load any part of the Developer Preview onto a mobile handset or wearable computing device or any other hardware device except an Android Wear device, combine any part of the Developer Preview with other software, or distribute any software or device incorporating a part of the Developer Preview.
-
-3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android Wear, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Developer Preview.
-
-3.5 Use, reproduction and distribution of components of the Developer Preview licensed under an open source software license are governed solely by the terms of that open source software license and not this License Agreement.
-
-3.6 You agree that the form and nature of the Developer Preview that Google provides may change without prior notice to you and that future versions of the Developer Preview may be incompatible with applications developed on previous versions of the Developer Preview. You agree that Google may stop (permanently or temporarily) providing the Developer Preview (or any features within the Developer Preview) to you or to users generally at Google's sole discretion, without prior notice to you.
-
-3.7 Nothing in this License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
-
-3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Developer Preview.
-
-3.9 Your use of any Android system files, packaged APIs, or other components of the Developer Preview which are part of the Android Software Development Kit is subject to the terms of the Android Software Development Kit License Agreement located at http://developer.android.com/sdk/terms.html. These terms are hereby incorporated by reference into this License Agreement.
-
-4. Use of the Developer Preview by You
-
-4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under this License Agreement in or to any software applications that you develop using the Developer Preview, including any intellectual property rights that subsist in those applications.
-
-4.2 You agree to use the Developer Preview and write applications only for purposes that are permitted by (a) this License Agreement, (b) the Google Play Developer Program Policies located at https://play.google.com/about/developer-content-policy.html, and hereby incorporated into this License Agreement by reference), and (c) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).  You agree to use reasonable efforts to comply with the Android Wear Platform Design Guide available on the Android Wear developer website
-
-4.3 You agree that if you use the Developer Preview to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so.
-
-4.4 You agree that you will not engage in any activity with the Developer Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google.
-
-4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android Wear and/or applications for Android Wear, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
-
-4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under this License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
-
-4.7 Unless otherwise specified in writing by Google, Google does not intend use of Android Wear to create obligations under the Health Insurance Portability and Accountability Act, as amended, (“HIPAA”), and makes no representations that Android Wear satisfies HIPAA requirements. If you are (or become) a Covered Entity or Business Associate under HIPAA, you agree not to use Android Wear for any purpose or in any manner involving Protected Health Information unless you have received prior written consent to such use from Google.
-
-4.8 The Developer Preview is in development, and your testing and feedback are an important part of the development process. By using the Developer Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Developer Preview, Android Wear devices, Android Wear system software, or Android Wear services having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Developer Preview as this Developer Preview will no longer be supported after the official SDK is released.
-
-5. Your Developer Credentials
-
-5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
-
-6. Privacy and Information
-
-6.1 In order to continually innovate and improve the Developer Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Developer Preview are being used and how they are being used. Before any of this information is collected, the Developer Preview will notify you and seek your consent. If you withhold consent, the information will not be collected.
-
-6.2 The data collected is examined in the aggregate to improve the Developer Preview and is maintained in accordance with Google's Privacy Policy lcoated at http://www.google.com/policies/privacy/.
-
-7. Third Party Applications
-
-7.1 If you use the Developer Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
-
-7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
-
-7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, this License Agreement does not affect your legal relationship with these third parties.
-
-8. Using Google APIs
-
-8.1 Google APIs
-
-8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
-
-8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
-
-9. Terminating this License Agreement
-
-9.1 This License Agreement will continue to apply until terminated by either you or Google as set out below.
-
-9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of the Developer Preview and any relevant developer credentials.
-
-9.3 Google may at any time, terminate this License Agreement with you if:
-(A) you have breached any provision of this License Agreement; or
-(B) Google is required to do so by law; or
-(C) the partner with whom Google offered certain parts of Developer Preview (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the Developer Preview to you; or
-(D) Google decides to no longer provide the Developer Preview or certain parts of the Developer Preview to users in the country in which you are resident or from which you use the service, or the provision of the Developer Preview or certain Developer Preview services to you by Google is, in Google's sole discretion, no longer commercially viable.
-
-9.4 When this License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst this License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely.
-
-10. DISCLAIMER OF WARRANTIES
-
-10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE DEVELOPER PREVIEW IS AT YOUR SOLE RISK AND THAT THE DEVELOPER PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
-
-10.2 YOUR USE OF THE DEVELOPER PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE DEVELOPER PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE.
-
-10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
-
-11. LIMITATION OF LIABILITY
-
-11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
-
-12. Indemnification
-
-12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Developer Preview, (b) any application you develop on the Developer Preview that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with this License Agreement.
-
-13. Changes to the License Agreement
-
-13.1 Google may make changes to the License Agreement as it distributes new versions of the Developer Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Developer Preview is made available.
-
-14. General Legal Terms
-
-14.1 This License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Developer Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Developer Preview.
-
-14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in this License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
-
-14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of this License Agreement is invalid, then that provision will be removed from this License Agreement without affecting the rest of this License Agreement. The remaining provisions of this License Agreement will continue to be valid and enforceable.
-
-14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to this License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of this License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to this License Agreement.
-
-14.5 EXPORT RESTRICTIONS. THE DEVELOPER PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE DEVELOPER PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
-
-14.6 The rights granted in this License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under this License Agreement without the prior written approval of the other party.
-
-14.7 This License Agreement, and your relationship with Google under this License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from this License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
-</div>
diff --git a/docs/html/wear/notifications/creating.jd b/docs/html/wear/notifications/creating.jd
deleted file mode 100644
index a5d7da7..0000000
--- a/docs/html/wear/notifications/creating.jd
+++ /dev/null
@@ -1,305 +0,0 @@
-page.title=Creating Notifications for Android Wear
-
-@jd:body
-
-
-<p>When an Android device such as a phone or tablet is connected to an Android wearable,
-all notifications are shared between the devices by default. On the Android wearable, each
-notification appears as a new card in the <a href="{@docRoot}wear/design/user-interface.html#Stream"
->context stream</a>.</p>
-
-<img src="{@docRoot}wear/images/notification_phone@2x.png" width="700" height="265" />
-
-
-<p>So without any effort, your app notifications are available to users on Android Wear.
-However, you can enhance the user experience in several ways. For instance,
-if users may respond to a notification by entering text, such as to reply to
-a message, you can add the ability for users to reply by voice directly from the
-wearable.</p>
-
-<p>To help you provide the best user experience
-for your notifications on Android Wear, this guide shows you how to
-build notifications using standard templates in
-the {@link android.support.v4.app.NotificationCompat.Builder} APIs, plus how to begin
-extending your notification's capabilities for the wearable user experience.</p>
-
-<p class="note"><strong>Note:</strong>
-Notifications using {@link android.widget.RemoteViews} are stripped of custom
-layouts and the system uses only the text and icons in the
-{@link android.app.Notification} object to
-display the notification in a card. However, custom card layouts will be supported by
-the official Android Wear SDK that is coming later.</p>
-</div>
-
-
-
-
-<h2 id="Import">Import the Necessary Classes</h2>
-
-<p>To begin development, you must first complete the instructions in the <a
-href="{@docRoot}wear/preview/start.html">Get Started with the Developer Preview</a> document.
-As mentioned in that document, your app must include
-both the <a href="http://developer.android.com/tools/support-library/features.html#v4">v4 support
-library</a> and the Developer Preview support library. So to get started,
-you should include the following imports in your project code:</p>
-
-<pre>
-import android.support.wearable.notifications.*;
-import android.support.wearable.app.NotificationManagerCompat;
-import android.support.v4.app.NotificationCompat;
-</pre>
-
-<p class="caution"><strong>Caution:</strong>
-The APIs in the current Android Wear Developer Preview are intended for <b>development and testing purposes only</b>, not for production apps. Google may change this Developer Preview significantly prior to the official release of the Android Wear SDK. You may not publicly distribute or ship any application using this Developer Preview, as this Developer Preview will no longer be supported after the official SDK is released (which will cause applications based only on the Developer Preview to break).</p>
-
-
-
-<h2 id="NotificationBuilder">Create Notifications with the Notification Builder</h2>
-
-<p>The <a href="http://developer.android.com/tools/support-library/features.html#v4">v4
-support library</a> allows you to create notifications using the latest notification features
-such as action buttons and large icons, while remaining compatible with Android 1.6 (API level
-4) and higher.</p>
-
-
-<p>For example, here's some code that creates and issues a notification using the
-{@link android.support.v4.app.NotificationCompat} APIs combined with the new
-<a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html">
-<code>NotificationManagerCompat</code></a> API:</p>
-
-
-<pre>
-int notificationId = 001;
-// Build intent for notification content
-Intent viewIntent = new Intent(this, ViewEventActivity.class);
-viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
-PendingIntent viewPendingIntent =
-        PendingIntent.getActivity(this, 0, viewIntent, 0);
-
-NotificationCompat.Builder notificationBuilder =
-        new NotificationCompat.Builder(this)
-        .setSmallIcon(R.drawable.ic_event)
-        .setContentTitle(eventTitle)
-        .setContentText(eventLocation)
-        .setContentIntent(viewPendingIntent);
-
-// Get an instance of the NotificationManager service
-NotificationManagerCompat notificationManager =
-        NotificationManagerCompat.from(this);
-
-// Build the notification and issues it with notification manager.
-notificationManager.notify(notificationId, notificationBuilder.build());
-</pre>
-
-<p>When this notification appears on a handheld device, the user can invoke the
-{@link android.app.PendingIntent}
-specified by the {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
-setContentIntent()} method by touching the notification. When this
-notification appears on an Android wearable, the user can swipe the notification to the left to
-reveal the <strong>Open</strong> action, which invokes the intent on the handheld device.</p>
-
-
-
-
-
-
-<img src="{@docRoot}wear/images/circle_email_action.png" height="200" style="float:right;clear:right;margin:0 0 20px 60px" />
-
-<h2 id="ActionButtons">Add Action Buttons</h2>
-
-<p>In addition to the primary content action defined by
-{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
-setContentIntent()}, you can add other actions by passing a {@link android.app.PendingIntent} to
-the {@link android.support.v4.app.NotificationCompat.Builder#addAction
-addAction()} method.</p>
-
-<p>For example, the following code shows the same type of notification from above, but adds an
-action to view the event location on a map.</p>
-
-<pre style="clear:right">
-// Build an intent for an action to view a map
-Intent mapIntent = new Intent(Intent.ACTION_VIEW);
-Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
-mapIntent.setData(geoUri);
-PendingIntent mapPendingIntent =
-        PendingIntent.getActivity(this, 0, mapIntent, 0);
-
-NotificationCompat.Builder notificationBuilder =
-        new NotificationCompat.Builder(this)
-        .setSmallIcon(R.drawable.ic_event)
-        .setContentTitle(eventTitle)
-        .setContentText(eventLocation)
-        .setContentIntent(viewPendingIntent)
-        <b>.addAction(R.drawable.ic_map,
-                getString(R.string.map), mapPendingIntent);</b>
-</pre>
-
-<p>On a handheld device, the action appears as an
-additional button attached to the notification. On an Android wearable, the action appears as
-a large button when the user swipes the notification to the left. When the user taps the action,
-the associated {@link android.content.Intent} is invoked on the handheld device.</p>
-
-<p class="note"><strong>Tip:</strong> If your notifications includes a "Reply" action
-  (such as for a messaging app), you can enhance the behavior by enabling
-  voice input replies directly from the Android wearable. For more information, read
-  <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from a Notification</a>.
-</p>
-
-<p>For details about designing action buttons (including the icon specifications), see the
-<a href="{@docRoot}wear/design/index.html#NotifictionActions">Design Principles of Android
-Wear</a>.</p>
-
-
-<h2 id="BigView">Add a Big View</h2>
-
-<img src="{@docRoot}wear/images/06_images.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>You can insert extended text content
-to your notification by adding one of the "big view" styles to your notification. On a
-handheld device, users can see the big view content by expanding the notification,
-while on Android Wear, the big view content is visible by default.</p>
-
-<p>To add the extended content to your notification, call {@link
-android.support.v4.app.NotificationCompat.Builder#setStyle setStyle()} on the {@link
-android.support.v4.app.NotificationCompat.Builder} object, passing it an instance of either
-{@link android.support.v4.app.NotificationCompat.BigTextStyle BigTextStyle} or
-{@link android.support.v4.app.NotificationCompat.InboxStyle InboxStyle}.</p>
-
-<p>For example, the following code adds an instance of
-{@link android.support.v4.app.NotificationCompat.BigTextStyle} to the event notification,
-in order to include the complete event description (which includes more text than can fit
-into the space provided for {@link android.support.v4.app.NotificationCompat.Builder#setContentText
-setContentText()}).</p>
-
-
-<pre style="clear:right">
-// Specify the 'big view' content to display the long
-// event description that may not fit the normal content text.
-BigTextStyle bigStyle = new NotificationCompat.BigTextStyle();
-bigStyle.bigText(eventDescription);
-
-NotificationCompat.Builder notificationBuilder =
-        new NotificationCompat.Builder(this)
-        .setSmallIcon(R.drawable.ic_event)
-        .setLargeIcon(BitmapFractory.decodeResource(
-                getResources(), R.drawable.notif_background))
-        .setContentTitle(eventTitle)
-        .setContentText(eventLocation)
-        .setContentIntent(viewPendingIntent)
-        .addAction(R.drawable.ic_map,
-                getString(R.string.map), mapPendingIntent)
-        <b>.setStyle(bigStyle);</b>
-</pre>
-
-<p>Notice that you can add a large background image to any notification using the
-{@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon setLargeIcon()}
-method. For more information about designing notifications with large images, see the
-<a href="{@docRoot}wear/design/index.html#Images">Design Principles of Android
-Wear</a>.</p>
-
-
-
-<h2 id="NewFeatures">Add New Features for Wearables</h2>
-
-<p>The Android Wear preview support library provides new APIs that
-  allow you to enhance the user experience for notifications on a wearable device. For example,
-  you can add additional pages of content that users can view by swiping to the left, or add the ability
-for users to deliver your app a text response using voice input.</p>
-
-<p>To use these new APIs:</p>
-
-<ol>
-  <li>Create an instance of
-{@link android.support.v4.app.NotificationCompat.Builder}, setting the
-desired properties for your notification.</li>
-  <li>Create a
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#WearableNotificationOptions.Builder(android.content.Context)"> <code>WearableNotificationOptions.Builder</code></a>, setting the wearable-specific options for the notication.</li>
-  <li>Call <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#WearableNotificationOptions.Builder#applyTo"><code>WearableNotificationOptions.Builder.applyTo()</code>
-  </a>, passing in the {@link android.support.v4.app.NotificationCompat.Builder}. This applies
-  the wearable options to the notification.</li>
-</ol>
-
-<p>
-For example, the following code calls the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setHintHideIcon(boolean)">
-  <code>setHintHideIcon()</code></a> method to remove the app icon from the notification card.
-</p>
-
-<pre>
-// Create a NotificationCompat.Builder for standard notification features
- NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
-         .setContentTitle("New mail from " + sender)
-         .setContentText(subject)
-         .setSmallIcon(R.drawable.new_mail);
-// Create a WearablesNotificationOptions.Builder to add functionality for wearables
- Notification notif = new WearableNotificationOptions.Builder()
-         <b>.setHintHideIcon(true)</b>
-         .build()
-         .applyTo(builder); //apply wearable options to to the original notification
-         .build()
-</pre>
-
-<p>The
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setHintHideIcon(boolean)">
-  <code>setHintHideIcon()</code></a> method is just one example of new notification features available with the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"
-  ><code>WearableNotificationOptions.Builder</code></a> class.
-</p>
-
-
-<p>When you want to deliver your notifications, always use the
-  <a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html">
-  <code>NotificationManagerCompat</code></a> API instead of
-  {@link android.app.NotificationManager}:</p>
-
-<pre>
-// Get an instance of the NotificationManager service
-NotificationManagerCompat notificationManager =
-        NotificationManagerCompat.from(this);
-
-// Issue the notification with notification manager.
-notificationManager.notify(notificationId, notif);
-</pre>
-
-
-<p>If you use the framework's {@link android.app.NotificationManager}, some
-features from <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"><code>WearableNotificationOptions.Builder</code></a>
-do not work.</p>
-
-
-<p>To continue enhancing your notifications for wearables using
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"
-  ><code>WearableNotificationOptions.Builder</code></a> and other APIs in the
-  preview support library, see the following developer guides:</p>
-
-  <dl>
-    <dt><a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input
-from a Notification</a></dt>
-      <dd>Add an action that receives voice input from the user and delivers the
-transcribed message to your app.</dd>
-    <dt><a href="{@docRoot}wear/notifications/pages.html">Adding Pages to a Notification</a></dt>
-      <dd>Add additional pages of information that are visible when the user
-swipes to the left.</dd>
-    <dt><a href="{@docRoot}wear/notifications/stacks.html">Stacking Notifications</a></dt>
-      <dd>Place all similar notifications from your app in a stack, allowing each to be
-viewed individually without adding multiple cards to the card stream.</dd>
-  </dl>
-
-
-<div class="next-docs">
-
-<div class="col-12">
-  <h2 class="norule">You might also want to read:</h2>
-  <dl>
-    <dt><a href="{@docRoot}training/notify-user/index.html">Notifying the User</a></dt>
-    <dd>Learn more about how to create notifications.</dd>
-    <dt><a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a></dt>
-    <dd>Learn everything you need to know about the {@link android.content.Intent}
-APIs, used by notificaton actions.</dd>
-  </dl>
-</div>
-</div>
-
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/pages.jd b/docs/html/wear/notifications/pages.jd
deleted file mode 100644
index 7d18b3f..0000000
--- a/docs/html/wear/notifications/pages.jd
+++ /dev/null
@@ -1,65 +0,0 @@
-page.title=Adding Pages to a Notification
-
-@jd:body
-
-
-<img src="{@docRoot}wear/images/09_pages.png" height="200" style="float:right;margin:0 0 20px 40px" />
-<img src="{@docRoot}wear/images/08_pages.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>When you'd like to provide more information without requiring users
-to open your app on their handheld device, you can
-add one or more pages to the notification on Android Wear. The additional pages
-appear immediately to the right of the main notification card.
-For information about when to use and how to design
-multiple pages, see the
-<a href="{@docRoot}wear/design/index.html#NotificationPages">Design Principles of Android
-Wear</a>.</p>
-
-<p>To create a notification with multiple pages:</p>
-<ol>
-    <li>Create the main notification (the first page) the way you'd like the notification to appear on a phone
-    or tablet.</li>
-    <li>Add pages one at a time with the
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addPage(android.app.Notification)">
-<code>addPage()</code></a> method, or add multiple pages in a {@link java.util.Collection} with the
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addPages(java.util.Collection<android.app.Notification>)">
-<code>addPages()</code></a> method.</li>
-    <li>Apply the pages to the main notification with the
-    <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.html#applyTo(android.support.v4.app.NotificationCompat.Builder)"
-    ><code>applyTo()</code></a> method.</li>
-</ol>
-
-
-<p>For example, here's some code that adds a second page to a notification:</p>
-
-<pre>
-// Create builder for the main notification
-NotificationCompat.Builder notificationBuilder =
-        new NotificationCompat.Builder(this)
-        .setSmallIcon(R.drawable.new_message)
-        .setContentTitle("Page 1")
-        .setContentText("Short message")
-        .setContentIntent(viewPendingIntent);
-
-// Create a big text style for the second page
-BigTextStyle secondPageStyle = new NotificationCompat.BigTextStyle();
-secondPageStyle.setBigContentTitle("Page 2")
-               .bigText("A lot of text...");
-
-// Create second page notification
-Notification secondPageNotification =
-        new NotificationCompat.Builder(this)
-        .setStyle(secondPageStyle)
-        .build();
-
-// Add second page with wearable options and apply to main notification
-Notification twoPageNotification =
-        new WearableNotificationsOptions.Builder()
-        .addPage(secondPageNotification)
-        .build()
-        .applyTo(notificationBuilder)
-        .build();
-</pre>
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/remote-input.jd b/docs/html/wear/notifications/remote-input.jd
deleted file mode 100644
index 4db8274..0000000
--- a/docs/html/wear/notifications/remote-input.jd
+++ /dev/null
@@ -1,241 +0,0 @@
-page.title=Receiving Voice Input from a Notification
-
-@jd:body
-
-<img src="{@docRoot}wear/images/13_voicereply.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
-
-<img src="{@docRoot}wear/images/03_actions.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
-
-<p>If your notification includes an action to respond with text,
-    such as to reply to an email, it should normally launch an activity
-    on the handheld device. However, when your notification appears on an Android wearable, you can
-    allow users to dictate a reply with voice input. You can also provide pre-defined text
-    messages for the user to select.</p>
-
-<p>When the user replies with voice or selects one of the available
-messages, the system sends the message to your app on the connected handheld device.
-The message is attached as an extra in the {@link android.content.Intent} you specified
-to be used for the notification action.</p>
-
-<p class="note"><strong>Note:</strong> When developing with the Android emulator,
-you must type text replies into the voice input field, so be sure you have enabled
-<strong>Hardware keyboard present</strong> in the AVD settings.</p>
-
-
-<h2 id="RemoteInput">Define the Remote Input</h2>
-
-<p>To create an action that supports voice input, first create an instance of
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html">
-<code>RemoteInput</code></a> using the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> APIs.
-    The
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> constructor takes a string that the system
-    will use as a key for the {@link android.content.Intent} extra that carries the reply message
-    to your app on the handheld.</p>
-
-<p>For example, here's how to create a new
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html">
-<code>RemoteInput</code></a> object that provides a custom
-    label for the voice input prompt:</p>
-
-<pre class="prettyprint">
-// Key for the string that's delivered in the action's intent
-private static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
-
-String replyLabel = getResources().getString(R.string.reply_label);
-
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
-        .setLabel(replyLabel)
-        .build();
-</pre>
-
-
-<h3>Add Pre-defined Text Responses</h3>
-
-<img src="{@docRoot}wear/images/12_voicereply.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>In addition to allowing voice input, you can
-    provide up to five text responses that the user can select for quick replies. Call
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html#setChoices(java.lang.String[])"><code>setChoices()</code></a> and pass it a string array.</p>
-
-<p>For example, you may define some responses in a resource array:</p>
-
-<p class="code-caption">res/values/strings.xml</code>
-<pre class="prettyprint">
-&lt;?xml version="1.0" encoding="utf-8"?>
-&lt;resources>
-    &lt;string-array name="reply_choices">
-        &lt;item>Yes&lt;/item>
-        &lt;item>No&lt;/item>
-        &lt;item>Maybe&lt;/item>
-    &lt;/string-array>
-&lt;/resources>
-</pre>
-
-<p>Then, inflate the string array and add it to the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a>:</p>
-
-<pre>
-String replyLabel = getResources().getString(R.string.reply_label);
-String[] replyChoices = getResources().getStringArray(R.array.reply_choices);
-
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
-        .setLabel(replyLabel)
-        .setChoices(replyChoices)
-        .build();
-</pre>
-
-
-
-
-<h2 id="PrimaryAction">Receive Voice Input for the Primary Action</h2>
-
-<p>If "Reply" is your notification's primary action (defined by the {@link
-android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}
-method), then you should attach the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to the main action using
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addRemoteInputForContentIntent(android.support.wearable.notifications.RemoteInput)">
-<code>addRemoteInputForContentIntent()</code></a>. For example:</p>
-
-<pre>
-// Create intent for reply action
-Intent replyIntent = new Intent(this, ReplyActivity.class);
-PendingIntent replyPendingIntent =
-        PendingIntent.getActivity(this, 0, replyIntent, 0);
-
-// Build the notification
-NotificationCompat.Builder replyNotificationBuilder =
-        new NotificationCompat.Builder(this)
-        .setSmallIcon(R.drawable.ic_new_message)
-        .setContentTitle("Message from Travis")
-        .setContentText("I love key lime pie!")
-        .setContentIntent(replyPendingIntent);
-
-// Create the remote input
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
-        .setLabel(replyLabel)
-        .build();
-
-// Add remote input to wearable options and apply to notification
-Notification replyNotification =
-        new WearableNotificationOptions.Builder()
-        .addRemoteInputForContentIntent(remoteInput)
-        .build()
-        .applyTo(replyNotificationBuilder)
-        .build();
-</pre>
-
-<p>By using
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addRemoteInputForContentIntent(android.support.wearable.notifications.RemoteInput)">
-<code>addRemoteInputForContentIntent()</code></a> to add the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> object to the notification's primary action,
-the button that normally appears as an "Open" action becomes the "Reply" action
-and starts the voice input UI when users select it on Android Wear.</p>
-
-
-
-<h2 id="NewAction">Receive Voice Input for a Secondary Action</h2>
-
-<p>If the "Reply" action is not your notification's primary action and you want to enable
-voice input for a secondary action, add the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to a new action button defined by an
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>Action</code></a> object.</p>
-
-<p>You should instantiate the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>WearableAction</code></a> with the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.Builder.html"><code>WearableAction.Builder()</code></a>
-constructor, which takes an icon and text label for the action button, plus the
-{@link android.app.PendingIntent}
-the system should use to invoke your app when the user selects the action. For example:</p>
-
-<pre>
-// Create the pending intent to fire when the user selects the action
-Intent replyIntent = new Intent(this, ReplyActivity.class);
-PendingIntent pendingReplyIntent =
-        PendingIntent.getActivity(this, 0, replyIntent, 0);
-
-// Create the remote input
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
-        .setLabel(replyLabel)
-        .build();
-
-// Create the notification action
-WearableAction replyAction = new WearableAction.Builder(R.drawable.ic_message,
-        "Reply", pendingIntent)
-        .addRemoteInput(remoteInput)
-        .build();
-</pre>
-
-
-<p>After you add the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>Wearablection</code></a>, set the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>WearableAction</code></a> on the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"><code>WearableNotifications.Builder</code></a> using
-  <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationsOptions.Builder.html#addAction(Action)"><code>addAction()</code></a>.
-For example:</p>
-
-<pre>
-// Create basic notification builder
-NotificationCompat.Builder replyNotificationBuilder =
-        new NotificationCompat.Builder(this)
-                .setContentTitle("New message");
-
-// Create the notification action and add remote input
-WearableAction replyAction = new WearableAction.Builder(R.drawable.ic_message,
-        "Reply", pendingIntent)
-        .addRemoteInput(remoteInput)
-        .build();
-
-// Create wearable notification and add action
-Notification replyNotification =
-        new WearableNotificationOptions.Builder()
-                .addAction(replyAction)
-                .build()
-                .applyTo(replyNotificationBuilder)
-                .build();
-</pre>
-
-
-<p>Now, when the user selects "Reply" from an Android wearable, the system prompts the user
-    for voice input (and shows the list of pre-defined replies, if provided).
-    Once the user completes a response, the system invokes
-    the {@link android.content.Intent} attached to the action and adds the
-<code>EXTRA_VOICE_REPLY</code> extra (the string
-    you passed to the
-  <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> constructor)
-  with the user's message as the string value.</p>
-
-<h2 id="ObtainInput">Obtaining the Voice Input as a String</h2>
-<p>To obtain the user's voice input, call
-<a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html#getResultsFromIntent(Intent)"><code>getResultsFromIntent()</code></a>,
-passing in the "Reply" action's intent. This method returns
-a {@link android.os.Bundle} that represents the intent's extras. You can then query the
-{@link android.os.Bundle} to obtain the user's voice input string.
-</p>
-<p>
-The following code shows a method that accepts an intent and returns the voice input string,
-which is referenced by the <code>EXTRA_VOICE_REPLY</code> key that is used in the previous examples:
-</p>
-<pre>
-/**
- * Obtain the intent that started this activity by calling
- * Activity.getIntent() and pass it into this method to
- * get the associated voice input string.
- */
-private String getMessageText(Intent intent) {
-    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
-        if (remoteInput != null) {
-            return remoteInput.getString(Intent.EXTRA_VOICE_REPLY);
-        }
-    }
-    return null;
-}
-</pre>
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/stacks.jd b/docs/html/wear/notifications/stacks.jd
deleted file mode 100644
index 3c3dc09..0000000
--- a/docs/html/wear/notifications/stacks.jd
+++ /dev/null
@@ -1,138 +0,0 @@
-page.title=Stacking Notifications
-
-@jd:body
-
-<img src="{@docRoot}wear/images/11_bundles_B.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" alt="" />
-<img src="{@docRoot}wear/images/11_bundles_A.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" alt="" />
-
-<p>When creating notifications for a handheld device, you should always aggregate similar
-notifications into a single summary notification. For example, if your app creates notifications
-for received messages, you should not show more than one notification
-on a handheld device&mdash;when more than one is message is received, use a single notification
-to provide a summary such as "2 new messages."</p>
-
-<p>However, a summary notification is less useful on an Android wearable because users
-are not able to read details from each message on the wearable (they must open your app on the
-handheld to view more information). So for the wearable device, you should
-group all the notifications together in a stack. The stack of notifications appears as a single
-card, which users can expand to view the details from each notification separately. The new
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a> method makes this possible while allowing you to still provide
-only one summary notification on the handheld device.</p>
-
-<p>For details about designing notification stacks, see the
-<a href="{@docRoot}wear/design/index.html#NotificationStacks">Design Principles of Android
-Wear</a>.</p>
-
-
-<h2 id="AddGroup">Add Each Notification to a Group</h2>
-
-<p>To create a stack, call <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a> for each notification you want in the stack and specify a
-group key. Then call <a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html#notify(int, android.app.Notification)"><code>notify()</code></a> to send it to the wearable.</p>
-
-<pre style="clear:right">
-final static String GROUP_KEY_EMAILS = "group_key_emails";
-
-// Build the notification
-NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
-         .setContentTitle("New mail from " + sender1)
-         .setContentText(subject1)
-         .setSmallIcon(R.drawable.new_mail);
-
-// Set the group with WearableNotificationOptions.Builder and apply to the notification
-Notification notif1 = new WearableNotificationOptions.Builder()
-         .setGroup(GROUP_KEY_EMAILS)
-         .build()
-         .applyTo(builder)
-         .build();
-
-// Issue the notification
-NotificationManagerCompat notificationManager =
-        NotificationManagerCompat.from(this);
-notificationManager.notify(notificationId1, notif);
-</pre>
-
-<p>Later on, when you create another notification, specify
-the same group key. When you call
-<a href="{@docRoot}reference/android/support/v4/app/NotificationManagerCompat.html#notify(int, android.app.Notification)"><code>notify()</code></a>,
-this notification appears in the same stack as the previous notification,
-instead of as a new card:</p>
-
-<pre style="clear:right">
-builder = new NotificationCompat.Builder(mContext)
-         .setContentTitle("New mail from " + sender2)
-         .setContentText(subject2)
-         .setSmallIcon(R.drawable.new_mail);
-
-// Use the same group as the previous notification
-Notification notif2 = new WearableNotificationOptions.Builder()
-         .setGroup(GROUP_KEY_EMAILS)
-         .build()
-         .applyTo(builder)
-         .build();
-
-notificationManager.notify(notificationId2, notif);
-</pre>
-
-<p>By default, notifications appear in the order in which you added them, with the most recent
-  notification visible at the top.  You can define a specific position in the group
-  by passing an order position as the second parameter for <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a>.</p>
-
-
-<h2 id="AddSummary">Add a Summary Notification</h2>
-
-<img src="{@docRoot}wear/images/notif_summary_framed.png" height="242" width="330" style="float:right;margin:0 0 20px 40px" alt="" />
-
-<p>It's important that you still provide a summary notification that appears on handheld devices.
-So in addition to adding each unique notification to the same stack group, also add a summary
-notification, but set its order position to be <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationsOptions.html#GROUP_ORDER_SUMMARY"><code>GROUP_ORDER_SUMMARY</code></a>.</p>
-
-<p>This notification does not appear in your stack of notifications on the wearable, but
-appears as the only notification on the handheld device.</p>
-
-<pre style="clear:right">
-Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
-        R.drawable.ic_large_icon);
-
-// Create an InboxStyle notification
-builder = new NotificationCompat.Builder(this)
-        .setContentTitle("2 new messages")
-        .setSmallIcon(R.drawable.ic_small_icon)
-        .setLargeIcon(largeIcon)
-        .setStyle(new NotificationCompat.InboxStyle()
-                .addLine("Alex Faaborg   Check this out")
-                .addLine("Jeff Chang   Launch Party")
-                .setBigContentTitle("2 new messages")
-                .setSummaryText("johndoe@gmail.com"));
-
-// Specify the notification to be the group summary
-Notification summaryNotification = new WearableNotificationOptions.Builder()
-        .setGroupSummary(GROUP_KEY_EMAILS)
-        .build()
-        .applyTo(builder)
-        .build();
-
-notificationManager.notify(notificationId3, summaryNotification);
-</pre>
-
-<p>
-This notification uses {@link android.support.v4.app.NotificationCompat.InboxStyle},
-which gives you an easy way to create notifications for email or messaging apps.
-You can use this style, another one defined in {@link android.support.v4.app.NotificationCompat},
-or no style for the summary notification.
-</p>
-
-<p class="note"><b>Tip:</b>
-To style the text like in the example screenshot, see
-<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithHTML">Styling
-with HTML markup</a> and
-<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithSpannables">Styling
-with Spannables</a>.
-</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/docs/html/wear/preview/signup.jd b/docs/html/wear/preview/signup.jd
deleted file mode 100644
index 8e8ec9a..0000000
--- a/docs/html/wear/preview/signup.jd
+++ /dev/null
@@ -1,169 +0,0 @@
-page.title=Sign Up for the Developer Preview
-
-@jd:body
-
-<p>To get started with the Android Wear Developer Preview, you must agree to the
-  following terms and conditions and provide the email address for your Google account.
-After signing up, you’ll have access to:</p>
-<ul>
-  <li>New APIs that allow you to build enhanced notifications for wearables.</li>
-  <li>Sample code using the new APIs.</li>
-  <li>The Android Wear Preview app that delivers your notifications to the Android Wear emulator.</li>
-</ul>
-
-<div class="sdk-terms" style="width:678px" onfocus="this.blur()"><div class="sdk-terms-padding">
-This is the Android Wear Developer Preview License Agreement.
-
-1. Introduction
-
-1.1 The Android Wear Developer Preview Kit (referred to in this License Agreement as the “Developer Preview” and specifically including the Android system files, packaged APIs, Developer Preview library files, and the Developer Preview companion app, if and when they are made available) is licensed to you subject to the terms of this License Agreement. This License Agreement forms a legally binding contract between you and Google in relation to your use of the Developer Preview.
-
-1.2 "Android Wear" means the Android Wear devices and the Android Wear software stack for use on Android Wear devices.
-
-1.3 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
-
-1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
-
-2. Accepting this License Agreement
-
-2.1 In order to use the Developer Preview, you must first agree to this License Agreement. You may not use the Developer Preview if you do not accept this License Agreement.
-
-2.2 By clicking to accept, you hereby agree to the terms of this License Agreement.
-
-2.3 You may not use the Developer Preview and may not accept the License Agreement if you are a person barred from receiving the Developer Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Developer Preview.
-
-2.4 If you are agreeing to be bound by this License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to this License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Developer Preview on behalf of your employer or other entity.
-
-3. Developer Preview License from Google
-
-3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide, royalty-free, internal-use, non-assignable and non-exclusive license to use the Developer Preview solely to develop applications to run on the Android Wear platform for Android Wear devices.
-
-3.2 You agree that Google or third parties own all legal right, title and interest in and to the Developer Preview, including any Intellectual Property Rights that subsist in the Developer Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
-
-3.3 You may not use the Developer Preview for any purpose not expressly permitted by this License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Developer Preview or any part of the Developer Preview; or (b) load any part of the Developer Preview onto a mobile handset or wearable computing device or any other hardware device except an Android Wear device, combine any part of the Developer Preview with other software, or distribute any software or device incorporating a part of the Developer Preview.
-
-3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android Wear, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Developer Preview.
-
-3.5 Use, reproduction and distribution of components of the Developer Preview licensed under an open source software license are governed solely by the terms of that open source software license and not this License Agreement.
-
-3.6 You agree that the form and nature of the Developer Preview that Google provides may change without prior notice to you and that future versions of the Developer Preview may be incompatible with applications developed on previous versions of the Developer Preview. You agree that Google may stop (permanently or temporarily) providing the Developer Preview (or any features within the Developer Preview) to you or to users generally at Google's sole discretion, without prior notice to you.
-
-3.7 Nothing in this License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
-
-3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Developer Preview.
-
-3.9 Your use of any Android system files, packaged APIs, or other components of the Developer Preview which are part of the Android Software Development Kit is subject to the terms of the Android Software Development Kit License Agreement located at http://developer.android.com/sdk/terms.html. These terms are hereby incorporated by reference into this License Agreement.
-
-4. Use of the Developer Preview by You
-
-4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under this License Agreement in or to any software applications that you develop using the Developer Preview, including any intellectual property rights that subsist in those applications.
-
-4.2 You agree to use the Developer Preview and write applications only for purposes that are permitted by (a) this License Agreement, (b) the Google Play Developer Program Policies located at https://play.google.com/about/developer-content-policy.html, and hereby incorporated into this License Agreement by reference), and (c) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).  You agree to use reasonable efforts to comply with the Android Wear Platform Design Guide available on the Android Wear developer website
-
-4.3 You agree that if you use the Developer Preview to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so.
-
-4.4 You agree that you will not engage in any activity with the Developer Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google.
-
-4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android Wear and/or applications for Android Wear, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
-
-4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under this License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
-
-4.7 Unless otherwise specified in writing by Google, Google does not intend use of Android Wear to create obligations under the Health Insurance Portability and Accountability Act, as amended, (“HIPAA”), and makes no representations that Android Wear satisfies HIPAA requirements. If you are (or become) a Covered Entity or Business Associate under HIPAA, you agree not to use Android Wear for any purpose or in any manner involving Protected Health Information unless you have received prior written consent to such use from Google.
-
-4.8 The Developer Preview is in development, and your testing and feedback are an important part of the development process. By using the Developer Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Developer Preview, Android Wear devices, Android Wear system software, or Android Wear services having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Developer Preview as this Developer Preview will no longer be supported after the official SDK is released.
-
-5. Your Developer Credentials
-
-5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
-
-6. Privacy and Information
-
-6.1 In order to continually innovate and improve the Developer Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Developer Preview are being used and how they are being used. Before any of this information is collected, the Developer Preview will notify you and seek your consent. If you withhold consent, the information will not be collected.
-
-6.2 The data collected is examined in the aggregate to improve the Developer Preview and is maintained in accordance with Google's Privacy Policy lcoated at http://www.google.com/policies/privacy/.
-
-7. Third Party Applications
-
-7.1 If you use the Developer Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
-
-7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
-
-7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, this License Agreement does not affect your legal relationship with these third parties.
-
-8. Using Google APIs
-
-8.1 Google APIs
-
-8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
-
-8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
-
-9. Terminating this License Agreement
-
-9.1 This License Agreement will continue to apply until terminated by either you or Google as set out below.
-
-9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of the Developer Preview and any relevant developer credentials.
-
-9.3 Google may at any time, terminate this License Agreement with you if:
-(A) you have breached any provision of this License Agreement; or
-(B) Google is required to do so by law; or
-(C) the partner with whom Google offered certain parts of Developer Preview (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the Developer Preview to you; or
-(D) Google decides to no longer provide the Developer Preview or certain parts of the Developer Preview to users in the country in which you are resident or from which you use the service, or the provision of the Developer Preview or certain Developer Preview services to you by Google is, in Google's sole discretion, no longer commercially viable.
-
-9.4 When this License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst this License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely.
-
-10. DISCLAIMER OF WARRANTIES
-
-10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE DEVELOPER PREVIEW IS AT YOUR SOLE RISK AND THAT THE DEVELOPER PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
-
-10.2 YOUR USE OF THE DEVELOPER PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE DEVELOPER PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE.
-
-10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
-
-11. LIMITATION OF LIABILITY
-
-11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
-
-12. Indemnification
-
-12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Developer Preview, (b) any application you develop on the Developer Preview that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with this License Agreement.
-
-13. Changes to the License Agreement
-
-13.1 Google may make changes to the License Agreement as it distributes new versions of the Developer Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Developer Preview is made available.
-
-14. General Legal Terms
-
-14.1 This License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Developer Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Developer Preview.
-
-14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in this License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
-
-14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of this License Agreement is invalid, then that provision will be removed from this License Agreement without affecting the rest of this License Agreement. The remaining provisions of this License Agreement will continue to be valid and enforceable.
-
-14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to this License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of this License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to this License Agreement.
-
-14.5 EXPORT RESTRICTIONS. THE DEVELOPER PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE DEVELOPER PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
-
-14.6 The rights granted in this License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under this License Agreement without the prior written approval of the other party.
-
-14.7 This License Agreement, and your relationship with Google under this License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from this License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
-
-
-</div></div>
-
-
-<p class="caution">
-  <strong>Important:</strong> Your email address is used to provide your Google account
-  access to the Android Wear Preview app Beta Preview on Google Play Store. As such, the
-  email address you provide below must be for the account you use to download apps on Google Play Store.
-  We may also use your email address to provide you with updates about the Android Wear
-  platform release.
-</p>
-
-<iframe src="https://docs.google.com/forms/d/1iSJ084kEkV242cZisNMnj6G8qpi9r_zdEyfXA-hB1ao/viewform?embedded=true" width="100%" height="540" frameborder="0" marginheight="0" marginwidth="0" id="signupform">Loading...</iframe>
-
-
-
-
-</body>
-</html>
diff --git a/docs/html/wear/preview/start.jd b/docs/html/wear/preview/start.jd
deleted file mode 100644
index f8f0129..0000000
--- a/docs/html/wear/preview/start.jd
+++ /dev/null
@@ -1,254 +0,0 @@
-page.title=Get Started with the Developer Preview
-
-@jd:body
-
-
-<div class="cols">
-
-  <div class="col-5">
-<p>The Android Wear Developer Preview includes tools and APIs that allow you to
-enhance your app notifications
-to provide an optimized user experience on Android wearables.</p>
-
-<p>With the Android Wear Developer Preview, you can:</p>
-
-<ul>
-  <li>Run the Android Wear platform in the Android emulator.</li>
-  <li>Connect your Android device to the emulator and view notifications from the
-device as cards on Android Wear.</li>
-  <li>Try new APIs in the preview support library that enhance your app's notifications
-with features such as voice replies and notification pages.</li>
-</ul>
-
-<p>To get access to the Developer Preview tools,
-click the sign up button on the right, then follow the setup instructions below.</p>
-  </div>
-
-  <div class="col-7">
-<img src="/wear/images/laptop-bridge.png" width="400" height="222" alt="" />
-
-<a href="/wear/preview/signup.html" class="button" style="
-    width: 370px;
-    margin: 10px 0 20px;
-    font-weight: bold;
-    font-size: 16px;
-">Sign Up for the Developer Preview</a>
-
-<p>Signing up provides you access to:</p>
-<ul>
-<li>New notification APIs in the preview support library.</li>
-<li>Sample apps using the new notification APIs.</li>
-<li>The <em>Android Wear Preview</em> app for your mobile device, which connects
-your device to the Android Wear emulator.</li>
-</ul>
-
-  </div>
-</div>
-
-
-<p class="caution"><strong>Caution:</strong>
-The current Android Wear Developer Preview is intended for <b>development and testing purposes only</b>, not for production apps. Google may change this Developer Preview significantly prior to the official release of the Android Wear SDK. You may not publicly distribute or ship any application using this Developer Preview, as this Developer Preview will no longer be supported after the official SDK is released (which will cause applications based only on the Developer Preview to break).</p>
-
-
-
-
-<h2 id="Prereq">Prerequisites</h2>
-
-<p>Before you begin the setup, you must:</p>
-
-<ol>
-  <li><a href="{@docRoot}sdk/index.html"><b>Install the Android SDK</b></a>.
-  <p>The Android SDK includes all the developer tools required to build
-apps for Android (optional IDEs are also available for download).</p></li>
-  <li><a href="{@docRoot}wear/preview/signup.html"><b>Sign up for the Android Wear Developer Preview</b></a>.
-  <p>You must sign up with a Gmail or other Google account in order to download the
-preview support library and receive access to the
-<em>Android Wear Preview</em> beta app on Google Play Store.</p></li>
-</ol>
-
-<p class="note"><strong>Note:</strong>
-If you're using the ADT plugin for Eclipse, you must update to version 22.6.1 or higher.
-If you're using Android Studio, you must update to version 0.5.1 or higher</p>
-
-
-
-<h2 id="Install">1. Install the Android Wear System Image</h2>
-
-
-<ol>
-  <li>Launch <a href="{@docRoot}tools/help/sdk-manager.html"
-    >Android SDK Manager</a>.
-  <ul>
-    <li>From Eclipse, select <b>Window > Android SDK Manager</b>.</li>
-    <li>From Android Studio, select <b>Tools > Android > SDK Manager</b>.</li>
-  </ul>
-  </li>
-  <li>Below Tools, verify that you have Android SDK Tools revision 22.6 or higher.
-    <p>If your version of Android SDK Tools is lower than 22.6, you must update:</p>
-    <ol>
-      <li>Select <strong>Android SDK Tools</strong>.</li>
-      <li>Click <strong>Install package</strong>.</li>
-      <li>Accept the license and click <strong>Install</strong>.</li>
-      <li>When the installation completes, restart Android SDK Manager.</li>
-    </ol>
-  </li>
-
-  <li>Below Android 4.4.2, select <strong>Android Wear ARM EABI v7a System Image</strong>.
-<p class="note"><strong>Note:</strong> Android Wear is designed to support multiple processor architectures.
-</p></li>
-  <li>Below Extras, ensure that you have the latest version of the
-<a href="{@docRoot}tools/support-library/index.html">Android Support Library</a>.
-    If an update is available, select <strong>Android Support Library</strong>. If you're using Android Studio, also select <strong>Android Support Repository</strong>.</li>
-  <li>Click <strong>Install packages</strong>.</li>
-  <li>Accept the license and click <strong>Install</strong>.</li>
-</ol>
-
-
-
-<h2 id="SetupEmulator">2. Set Up the Android Wear Emulator</h2>
-
-<ol>
-<li>Launch the <a href="{@docRoot}tools/help/avd-manager.html"
-  >Android Virtual Device Manager</a>.
-<ul>
-<li>From Eclipse, select <b>Window > Android Virtual Device Manager</b>.</li>
-<li>From Android Studio, select <b>Tools > Android > AVD Manager</b>.</li>
-</ul>
-</li>
-<li>Click <strong>New</strong>.</li>
-<li>For the AVD Name, enter "AndroidWearSquare" or "AndroidWearRound", depending on whether
-you want to create an emulator with a square or round display.</li>
-<li>For the Device, select <strong>Android Wear Square</strong> or
-	<strong>Android Wear Round</strong>.</li>
-<li>For the Target, select <strong>Android 4.4.2 - API Level 19</strong> (or higher).</li>
-<li>For the CPU/ABI, select <strong>Android Wear ARM (armeabi-v7a)</strong>.
-<p class="note"><strong>Note:</strong> Android Wear is designed to support multiple processor architectures.
-</p></li>
-<li>For the Skin, select <strong>AndroidWearSquare</strong> or
-<strong>AndroidWearRound</strong>.</li>
-<li>Leave all other options set to their defaults and click <strong>OK</strong>.
-  <p>Although real Android wearables do not provide a keyboard as an input method,
-    you should keep <strong>Hardware keyboard present</strong> selected so you can
-    provide text input on screens where users will instead provide voice input.</p>
-</li>
-<!--
-<li>Click <strong>Device Definitions</strong>.</li>
-<li>Select <strong>Android WearSquare</strong> then click <strong>Create AVD</strong>.</li>
-<li>Click <strong>OK</strong>.</li>
--->
-<li>In the list of AVDs, select the one you just created and click
- <strong>Start</strong>. In the following window, click <strong>Launch</strong>.</li>
-</ol>
-
-<p>The Android Wear emulator now starts. To begin testing your app's notifications,
-you must now pair the emulator to your development device
-that has the <em>Android Wear Preview</em> app installed.</p>
-
-<p class="note"><strong>Tip:</strong> To improve the emulator startup time, edit your AVD
-and enable <strong>Snapshot</strong> under Emulator Options. When you start the emulator,
-select <strong>Save to snapshot</strong> then click <strong>Launch</strong>. Once the emulator
-is running, close it to save a snapshot of the system.
-Start the AVD again, but select <strong>Launch from snapshot</strong> and
-deselect <strong>Save to snapshot</strong>.</p>
-
-<p class="caution"><strong>Caution:</strong> Do not install apps on the Android Wear emulator.
-The system does not support traditional Android apps and the result of running such apps is
-unpredictable.</p>
-
-
-
-<h2 id="SetupApp">3. Set Up the Android Wear Preview App</h2>
-
-<p>To view your app's notifications on the Android Wear emulator, you must have the
-<em>Android Wear Preview</em> app installed on your Android device (a phone or tablet).</p>
-
-<p>To receive the Android Wear Preview app, you must <a
-href="{@docRoot}wear/preview/signup.html">sign up for the Developer Preview</a> using the same
-Gmail or Google account you use with Google Play Store.</p>
-</p>
-
-<p class="note"><strong>Note:</strong> The <em>Android Wear Preview</em> app is compatible with
-    Android 4.3 and higher and is not available for the Android emulator.</p>
-
-<p>After you've signed up for the Developer Preview,
-  you'll receive a confirmation email that includes a link to opt-in to the
-  <em>Android Wear Preview</em> app beta program. Once you opt-in, it may take up to 24 hours for the
-  app to become available in Google Play Store.</p>
-
-<p>After you install the <em>Android Wear Preview</em> app, you can set up
-  your device to communicate with the Android Wear emulator:</p>
-
-<ol>
-<li>Open the <em>Android Wear Preview</em> app. You should see a notice that the app is currently
-  not enabled as a notification listener. Tap the message to open the system settings,
-  then select Android Wear Preview to grant it notification access.</li>
-<li>Connect your device to your development machine over USB. Be sure that no other
- Android devices are connected to the machine.</li>
-<li>Ensure that the Android Wear emulator (created in the previous section) is running.
-The emulator should show the time and an icon that indicates no device is connected.</li>
-<li>Open a command line terminal, navigate to your Android SDK's <code>platform-tools/</code>
-directory, then execute:
-<pre style="margin-top:.5em">adb -d forward tcp:5601 tcp:5601</pre>
-<p class="note"><strong>Note:</strong> You must execute this command each time you connect your
-device over USB.</p>
-</li>
-<li>Return to the Android Wear Preview app. It should now indicate that it is connected to
-  the emulator. The Android Wear emulator should now show the 'g' orb icon, indicating
-  that is is connected to your device.
-</ol>
-
-<p>Now, notifications from your device also appear in the Android Wear emulator.</p>
-
-
-
-
-<h2 id="AddLibrary">4. Add the Support Library to Your Project</h2>
-
-<p>The Android Wear preview support library includes several APIs that allow you to
-optimize your app's notifications for the Android Wear user experience.</p>
-
-<p>To receive the preview support library, you must <a
-href="{@docRoot}wear/preview/signup.html">sign up for the Developer Preview</a>. The
-confirmation email you receive after you sign up includes a link to download a ZIP file,
-which contains the preview support library and some sample apps.</p>
-
-<p>After you download and unzip the package, add the preview support library
-sto your Android project:</p>
-
-<p><b>If you're using Eclipse:</b></p>
-    <ol>
-      <li>In your Android app project, create a <code>libs/</code> directory in your project root
-    (the same location as the <code>AndroidManifest.xml</code> file).</li>
-      <li>Copy the v4 support library JAR file from your Android SDK directory (e.g.,
-        <code>&lt;sdk&gt;/extras/android/support/v4/android-support-v4.jar</code>) into your
-        project <code>libs/</code> directory.
-      <li>Also save the <code>wearable-preview-support.jar</code> file in the <code>libs/</code> directory.
-      <li>Right click each JAR file and select <strong>Build Path &gt; Add to Build Path</strong>.</li>
-    </ol>
-
- <p><b>If you're using Android Studio:</b></p>
-    <ol>
-     <li>In your Android app project, create a <code>libs/</code> directory in your project root
-    (the same location as the <code>AndroidManifest.xml</code> file).</li>
-      <li>Save the <code>wearable-preview-support.jar</code> file in the <code>libs/</code> directory.
-      <li>Open the <code>build.gradle</code> file in your app module.</li>
-      <li>Add a dependency rule for both the v4 support library and the Android Wear
-      preview support library:
-<pre>
-dependencies {
-    compile "com.android.support:support-v4:18.0.+"
-    compile files('../libs/wearable-preview-support.jar')
-}
-</pre>
-      </li>
-      <li>Click <strong>Sync Project with Gradle Files</strong> in the toolbar.</li>
-    </ol>
-
-<p>To start optimizing your notifications for Android Wear,
-  read <a href="{@docRoot}wear/notifications/creating.html"
-  >Creating Notifications for Android Wear</a>.</p>
-
-
-
-</body>
-</html>
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..99596ef 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
@@ -59,11 +65,11 @@
 
     /**
      * Used to determine when compatibility scaling is in effect.
-     * 
+     *
      * @hide
      */
     protected int mScreenDensity = Bitmap.DENSITY_NONE;
-    
+
     // Used by native code
     @SuppressWarnings("UnusedDeclaration")
     private int mSurfaceFormat;
@@ -73,7 +79,7 @@
      * @hide
      */
     public static final int DIRECTION_LTR = 0;
-    
+
     /**
      * Flag for drawTextRun indicating right-to-left run direction.
      * @hide
@@ -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;
         }
@@ -130,19 +136,19 @@
     /**
      * Construct a canvas with the specified bitmap to draw into. The bitmap
      * must be mutable.
-     * 
+     *
      * <p>The initial target density of the canvas is the same as the given
      * bitmap's density.
      *
      * @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.
@@ -185,10 +177,10 @@
 
     /**
      * Indicates whether this Canvas uses hardware acceleration.
-     * 
+     *
      * Note that this method does not define what type of hardware acceleration
      * may or may not be used.
-     * 
+     *
      * @return True if drawing operations are hardware accelerated,
      *         false otherwise.
      */
@@ -197,7 +189,7 @@
     }
 
     /**
-     * Specify a bitmap for the canvas to draw into. All canvas state such as 
+     * Specify a bitmap for the canvas to draw into. All canvas state such as
      * layers, filters, and the save/restore stack are reset with the exception
      * of the current matrix and clip stack. Additionally, as a side-effect
      * the canvas' target density is updated to match that of the bitmap.
@@ -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
@@ -274,7 +279,7 @@
      * to determine the scaling factor when drawing a bitmap into it.
      *
      * @see #setDensity(int)
-     * @see Bitmap#getDensity() 
+     * @see Bitmap#getDensity()
      */
     public int getDensity() {
         return mDensity;
@@ -290,7 +295,7 @@
      * {@link Bitmap#DENSITY_NONE} to disable bitmap scaling.
      *
      * @see #getDensity()
-     * @see Bitmap#setDensity(int) 
+     * @see Bitmap#setDensity(int)
      */
     public void setDensity(int density) {
         if (mBitmap != null) {
@@ -308,19 +313,19 @@
      * Returns the maximum allowed width for bitmaps drawn with this canvas.
      * Attempting to draw with a bitmap wider than this value will result
      * in an error.
-     * 
-     * @see #getMaximumBitmapHeight() 
+     *
+     * @see #getMaximumBitmapHeight()
      */
     public int getMaximumBitmapWidth() {
         return MAXMIMUM_BITMAP_SIZE;
     }
-    
+
     /**
      * Returns the maximum allowed height for bitmaps drawn with this canvas.
      * Attempting to draw with a bitmap taller than this value will result
      * in an error.
-     * 
-     * @see #getMaximumBitmapWidth() 
+     *
+     * @see #getMaximumBitmapWidth()
      */
     public int getMaximumBitmapHeight() {
         return MAXMIMUM_BITMAP_SIZE;
@@ -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 */
@@ -339,8 +357,8 @@
     /** clip against the layer's bounds */
     public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
     /** restore everything when restore() is called */
-    public static final int ALL_SAVE_FLAG = 0x1F; 
-    
+    public static final int ALL_SAVE_FLAG = 0x1F;
+
     /**
      * Saves the current matrix and clip onto a private stack. Subsequent
      * calls to translate,scale,rotate,skew,concat or clipRect,clipPath
@@ -350,8 +368,10 @@
      *
      * @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
      * stack. Subsequent calls to translate,scale,rotate,skew,concat or
@@ -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,35 +586,35 @@
      *
      * @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);
     }
-    
+
     /**
      * Completely replace the current matrix with the specified matrix. If the
      * matrix parameter is null, then the current matrix is reset to identity.
-     * 
+     *
      * <strong>Note:</strong> it is recommended to use {@link #concat(Matrix)},
      * {@link #scale(float, float)}, {@link #translate(float, float)} and
      * {@link #rotate(float)} instead of this method.
      *
      * @param matrix The matrix to replace the current matrix with. If it is
      *               null, set the current matrix to identity.
-     *               
-     * @see #concat(Matrix) 
+     *
+     * @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);
     }
-    
+
     /**
      * Return, in ctm, the current transformation matrix. This does not alter
      * 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,13 +622,13 @@
      * matrix.
      */
     @Deprecated
-    public final Matrix getMatrix() {
+    public final @NonNull Matrix getMatrix() {
         Matrix m = new Matrix();
         //noinspection deprecation
         getMatrix(m);
         return m;
     }
-    
+
     /**
      * Modify the current clip with the specified rectangle.
      *
@@ -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,8 +673,11 @@
      * @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
      * expressed in local coordinates.
@@ -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,20 +741,20 @@
      * @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);
     }
-    
+
     /**
      * Intersect the current clip with the specified path.
      *
      * @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);
     }
-    
+
     /**
      * Modify the current clip with the specified region. Note that unlike
      * clipRect() and clipPath() which transform their arguments by the
@@ -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 {
@@ -756,7 +814,7 @@
          * Antialiased: Treat edges by rounding-out, since they may be antialiased
          */
         AA(1);
-        
+
         EdgeType(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -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,21 +897,21 @@
      *               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);
     }
-    
+
     /**
      * Retrieve the bounds of the current clip (in local coordinates).
      *
      * @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;
     }
-    
+
     /**
      * Fill the entire canvas' bitmap (restricted to the current clip) with the
      * specified RGB color, using srcover porterduff mode.
@@ -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,10 +965,10 @@
      *
      * @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);
     }
-    
+
     /**
      * Draw a series of points. Each point is centered at the coordinate
      * specified by pts[], and its diameter is specified by the paint's stroke
@@ -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,10 +1062,10 @@
      * @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);
     }
-    
+
 
     /**
      * Draw the specified Rect using the specified paint. The rectangle will
@@ -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,19 @@
      *
      * @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);
+        drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+    }
+
+    /**
+     * Draw the specified oval using the specified paint. The oval will be
+     * filled or framed based on the Style in the paint.
+     */
+    public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        native_drawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.mNativePaint);
     }
 
     /**
@@ -1037,22 +1112,22 @@
      * @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);
     }
 
     /**
      * <p>Draw the specified arc, which will be scaled to fit inside the
      * specified oval.</p>
-     * 
+     *
      * <p>If the start angle is negative or >= 360, the start angle is treated
      * as start angle modulo 360.</p>
-     * 
+     *
      * <p>If the sweep angle is >= 360, then the oval is drawn
      * completely. Note that this differs slightly from SkPath::arcTo, which
      * treats the sweep angle modulo 360. If the sweep angle is negative,
      * the sweep angle is treated as sweep angle modulo 360</p>
-     * 
+     *
      * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the
      * geometric angle of 0 degrees (3 o'clock on a watch.)</p>
      *
@@ -1064,12 +1139,36 @@
                         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) {
-        if (oval == null) {
-            throw new NullPointerException();
-        }
-        native_drawArc(mNativeCanvas, oval, startAngle, sweepAngle,
+    public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+            @NonNull Paint paint) {
+        drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+                paint);
+    }
+
+    /**
+     * <p>Draw the specified arc, which will be scaled to fit inside the
+     * specified oval.</p>
+     *
+     * <p>If the start angle is negative or >= 360, the start angle is treated
+     * as start angle modulo 360.</p>
+     *
+     * <p>If the sweep angle is >= 360, then the oval is drawn
+     * completely. Note that this differs slightly from SkPath::arcTo, which
+     * treats the sweep angle modulo 360. If the sweep angle is negative,
+     * the sweep angle is treated as sweep angle modulo 360</p>
+     *
+     * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the
+     * geometric angle of 0 degrees (3 o'clock on a watch.)</p>
+     *
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     * @param useCenter If true, include the center of the oval in the arc, and
+                        close it if it is being stroked. This will draw a wedge
+     * @param paint      The paint used to draw the arc
+     */
+    public void drawArc(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+        native_drawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
                 useCenter, paint.mNativePaint);
     }
 
@@ -1082,7 +1181,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 +1194,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 +1205,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);
     }
 
     /**
@@ -1130,10 +1229,10 @@
      * @param patch The ninepatch object to render
      * @param dst The destination rectangle.
      * @param paint The paint to draw the bitmap with. may be null
-     * 
+     *
      * @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,14 +1245,14 @@
      *
      * @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);
     }
 
     /**
      * Draw the specified bitmap, with its top/left corner at (x,y), using
      * the specified paint, transformed by the current matrix.
-     * 
+     *
      * <p>Note: if the paint contains a maskfilter that generates a mask which
      * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
      * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
@@ -1163,15 +1262,15 @@
      * <p>If the bitmap and canvas have different densities, this function
      * will take care of automatically scaling the bitmap to draw at the
      * same density as the canvas.
-     * 
+     *
      * @param bitmap The bitmap to be drawn
      * @param left   The position of the left side of the bitmap being drawn
      * @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);
     }
 
@@ -1179,7 +1278,7 @@
      * Draw the specified bitmap, scaling/translating automatically to fill
      * the destination rectangle. If the source rectangle is not null, it
      * specifies the subset of the bitmap to draw.
-     * 
+     *
      * <p>Note: if the paint contains a maskfilter that generates a mask which
      * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
      * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
@@ -1190,19 +1289,20 @@
      * This is because the source and destination rectangle coordinate
      * spaces are in their respective densities, so must already have the
      * appropriate scaling factor applied.
-     * 
+     *
      * @param bitmap The bitmap to be drawn
      * @param src    May be null. The subset of the bitmap to be drawn
      * @param dst    The rectangle that the bitmap will be scaled/translated
      *               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);
     }
 
@@ -1210,7 +1310,7 @@
      * Draw the specified bitmap, scaling/translating automatically to fill
      * the destination rectangle. If the source rectangle is not null, it
      * specifies the subset of the bitmap to draw.
-     * 
+     *
      * <p>Note: if the paint contains a maskfilter that generates a mask which
      * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter),
      * then the bitmap will be drawn as if it were in a Shader with CLAMP mode.
@@ -1221,22 +1321,23 @@
      * This is because the source and destination rectangle coordinate
      * spaces are in their respective densities, so must already have the
      * appropriate scaling factor applied.
-     * 
+     *
      * @param bitmap The bitmap to be drawn
      * @param src    May be null. The subset of the bitmap to be drawn
      * @param dst    The rectangle that the bitmap will be scaled/translated
      *               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);
     }
-    
+
     /**
      * Treat the specified array of colors as a bitmap, and draw it. This gives
      * the same result as first creating a bitmap from the array, and then
@@ -1262,8 +1363,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 +1386,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 +1399,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 +1413,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);
     }
 
@@ -1325,7 +1426,7 @@
             throw new ArrayIndexOutOfBoundsException();
         }
     }
-    
+
     /**
      * Draw the bitmap through the mesh, where mesh vertices are evenly
      * distributed across the bitmap. There are meshWidth+1 vertices across, and
@@ -1352,8 +1453,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 +1469,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);
     }
@@ -1376,7 +1478,7 @@
         TRIANGLES(0),
         TRIANGLE_STRIP(1),
         TRIANGLE_FAN(2);
-        
+
         VertexMode(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -1386,7 +1488,7 @@
          */
         public final int nativeInt;
     }
-    
+
     /**
      * Draw the array of vertices, interpreted as triangles (based on mode). The
      * verts array is required, and specifies the x,y pairs for each vertex. If
@@ -1415,11 +1517,12 @@
      * @param indices If not null, array of indices to reference into the
      *      vertex (texs, colors) array.
      * @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. 
+     * @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 +1533,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 +1547,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 +1566,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 +1582,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 +1604,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 +1616,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);
         }
@@ -1521,7 +1627,7 @@
      * bidi on the provided text, but renders it as a uniform right-to-left or
      * left-to-right run, as indicated by dir. Alignment of the text is as
      * determined by the Paint's TextAlign value.
-     * 
+     *
      * @param text the text to render
      * @param index the start of the text to render
      * @param count the count of chars to render
@@ -1532,13 +1638,12 @@
      *         + count.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
-     * @param dir the run direction, either {@link #DIRECTION_LTR} or
-     *         {@link #DIRECTION_RTL}.
+     * @param isRtl whether the run is in RTL direction
      * @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, boolean isRtl, @NonNull Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1549,12 +1654,9 @@
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
-            throw new IllegalArgumentException("unknown dir: " + dir);
-        }
 
-        native_drawTextRun(mNativeCanvas, text, index, count,
-                contextIndex, contextCount, x, y, dir, paint.mNativePaint, paint.mNativeTypeface);
+        native_drawTextRun(mNativeCanvasWrapper, text, index, count,
+                contextIndex, contextCount, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
     }
 
     /**
@@ -1570,12 +1672,12 @@
      *            position can be used for shaping context.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
-     * @param dir the run direction, either 0 for LTR or 1 for RTL.
+     * @param isRtl whether the run is in RTL direction
      * @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, boolean isRtl, @NonNull Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1587,22 +1689,20 @@
             throw new IndexOutOfBoundsException();
         }
 
-        int flags = dir == 0 ? 0 : 1;
-
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
-            native_drawTextRun(mNativeCanvas, text.toString(), start, end,
-                    contextStart, contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+            native_drawTextRun(mNativeCanvasWrapper, text.toString(), start, end,
+                    contextStart, contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawTextRun(this, start, end,
-                    contextStart, contextEnd, x, y, flags, paint);
+                    contextStart, contextEnd, x, y, isRtl, paint);
         } else {
             int contextLen = contextEnd - contextStart;
             int len = end - start;
             char[] buf = TemporaryBuffer.obtain(contextLen);
             TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
-            native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
-                    0, contextLen, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
+            native_drawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+                    0, contextLen, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1610,9 +1710,10 @@
     /**
      * Draw the text in the array, with each character's origin specified by
      * the pos array.
-     * 
+     *
      * This method does not support glyph composition and decomposition and
-     * should therefore not be used to render complex scripts.
+     * should therefore not be used to render complex scripts. It also doesn't
+     * handle supplementary characters (eg emoji).
      *
      * @param text     The text to be drawn
      * @param index    The index of the first character to draw
@@ -1622,31 +1723,31 @@
      * @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,
-                paint.mNativePaint);
+        for (int i = 0; i < count; i++) {
+            drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+        }
     }
 
     /**
      * Draw the text in the array, with each character's origin specified by
      * the pos array.
-     * 
+     *
      * This method does not support glyph composition and decomposition and
-     * should therefore not be used to render complex scripts.
+     * should therefore not be used to render complex scripts. It also doesn't
+     * handle supplementary characters (eg emoji).
      *
      * @param text  The text to be drawn
      * @param pos   Array of [x,y] positions, used to position each character
      * @param paint The paint used for the text (e.g. color, size, style)
      */
     @Deprecated
-    public void drawPosText(String text, float[] pos, Paint paint) {
-        if (text.length()*2 > pos.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-        native_drawPosText(mNativeCanvas, text, pos, paint.mNativePaint);
+    public void drawPosText(@NonNull String text, @NonNull float[] pos, @NonNull Paint paint) {
+        drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
     }
 
     /**
@@ -1662,14 +1763,14 @@
      *                 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);
+                paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
     }
 
     /**
@@ -1685,10 +1786,11 @@
      *                 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,
-                    paint.mBidiFlags, paint.mNativePaint);
+            native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset,
+                    paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
         }
     }
 
@@ -1700,20 +1802,20 @@
      * <p>
      * <strong>Note:</strong> This forces the picture to internally call
      * {@link Picture#endRecording} in order to prepare for playback.
-     * 
+     *
      * @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);
         restoreToCount(restoreCount);
     }
-    
+
     /**
      * 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) {
@@ -1722,11 +1824,11 @@
         drawPicture(picture);
         restore();
     }
-    
+
     /**
      * 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 +1863,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 +1912,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,23 +1925,29 @@
                                                 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,
                                                long nativePaint);
-    private static native void native_drawOval(long nativeCanvas, RectF oval,
-                                               long nativePaint);
+    private static native void native_drawOval(long nativeCanvas, float left, float top,
+                                               float right, float bottom, long nativePaint);
     private static native void native_drawCircle(long nativeCanvas, float cx,
                                                  float cy, float radius,
                                                  long nativePaint);
-    private static native void native_drawArc(long nativeCanvas, RectF oval,
-                                              float startAngle, float sweep,
-                                              boolean useCenter,
+    private static native void native_drawArc(long nativeCanvas, float left, float top,
+                                              float right, float bottom,
+                                              float startAngle, float sweep, boolean useCenter,
                                               long nativePaint);
     private static native void native_drawRoundRect(long nativeCanvas,
             float left, float top, float right, float bottom,
@@ -1886,29 +2003,22 @@
 
     private static native void native_drawTextRun(long nativeCanvas, String text,
             int start, int end, int contextStart, int contextEnd,
-            float x, float y, int flags, long nativePaint, long nativeTypeface);
+            float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
 
     private static native void native_drawTextRun(long nativeCanvas, char[] text,
             int start, int count, int contextStart, int contextCount,
-            float x, float y, int flags, long nativePaint, long nativeTypeface);
+            float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
 
-    private static native void native_drawPosText(long nativeCanvas,
-                                                  char[] text, int index,
-                                                  int count, float[] pos,
-                                                  long nativePaint);
-    private static native void native_drawPosText(long nativeCanvas,
-                                                  String text, float[] pos,
-                                                  long nativePaint);
     private static native void native_drawTextOnPath(long nativeCanvas,
                                                      char[] text, int index,
                                                      int count, long nativePath,
                                                      float hOffset,
                                                      float vOffset, int bidiFlags,
-                                                     long nativePaint);
+                                                     long nativePaint, long nativeTypeface);
     private static native void native_drawTextOnPath(long nativeCanvas,
                                                      String text, long nativePath,
                                                      float hOffset,
                                                      float vOffset,
-                                                     int flags, long nativePaint);
+                                                     int flags, long nativePaint, long nativeTypeface);
     private static native void finalizer(long nativeCanvas);
 }
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/Paint.java b/graphics/java/android/graphics/Paint.java
index 4268a24..72e39bb 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -56,7 +56,7 @@
      * @hide
      */
     public  int         mBidiFlags = BIDI_DEFAULT_LTR;
-    
+
     static final Style[] sStyleArray = {
         Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
     };
@@ -202,14 +202,14 @@
 
     /**
      * Bidi flag to set LTR paragraph direction.
-     * 
+     *
      * @hide
      */
     public static final int BIDI_LTR = 0x0;
 
     /**
      * Bidi flag to set RTL paragraph direction.
-     * 
+     *
      * @hide
      */
     public static final int BIDI_RTL = 0x1;
@@ -217,7 +217,7 @@
     /**
      * Bidi flag to detect paragraph direction via heuristics, defaulting to
      * LTR.
-     * 
+     *
      * @hide
      */
     public static final int BIDI_DEFAULT_LTR = 0x2;
@@ -225,21 +225,21 @@
     /**
      * Bidi flag to detect paragraph direction via heuristics, defaulting to
      * RTL.
-     * 
+     *
      * @hide
      */
     public static final int BIDI_DEFAULT_RTL = 0x3;
 
     /**
      * Bidi flag to override direction to all LTR (ignore bidi).
-     * 
+     *
      * @hide
      */
     public static final int BIDI_FORCE_LTR = 0x4;
 
     /**
      * Bidi flag to override direction to all RTL (ignore bidi).
-     * 
+     *
      * @hide
      */
     public static final int BIDI_FORCE_RTL = 0x5;
@@ -331,7 +331,7 @@
          * either FILL or STROKE.
          */
         FILL_AND_STROKE (2);
-        
+
         Style(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -357,7 +357,7 @@
          * of the path.
          */
         SQUARE  (2);
-        
+
         private Cap(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -381,7 +381,7 @@
          * The outer edges of a join meet with a straight line
          */
         BEVEL   (2);
-        
+
         private Join(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -405,7 +405,7 @@
          * The text is drawn to the left of the x,y origin
          */
         RIGHT   (2);
-        
+
         private Align(int nativeInt) {
             this.nativeInt = nativeInt;
         }
@@ -418,7 +418,7 @@
     public Paint() {
         this(0);
     }
-    
+
     /**
      * Create a new paint with the specified flags. Use setFlags() to change
      * these after the paint is created.
@@ -475,7 +475,7 @@
         setTextLocale(Locale.getDefault());
         setElegantTextHeight(false);
     }
-    
+
     /**
      * Copy the fields from src into this paint. This is equivalent to calling
      * get() on all of the src fields, and calling the corresponding set()
@@ -529,7 +529,7 @@
 
     /**
      * Return the bidi flags on the paint.
-     * 
+     *
      * @return the bidi flags on the paint
      * @hide
      */
@@ -552,7 +552,7 @@
 
     /**
      * Return the paint's flags. Use the Flag enum to test flag values.
-     * 
+     *
      * @return the paint's flags (see enums ending in _Flag for bit masks)
      */
     public native int getFlags();
@@ -587,7 +587,7 @@
     public final boolean isAntiAlias() {
         return (getFlags() & ANTI_ALIAS_FLAG) != 0;
     }
-    
+
     /**
      * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit
      * AntiAliasing smooths out the edges of what is being drawn, but is has
@@ -597,7 +597,7 @@
      * @param aa true to set the antialias bit in the flags, false to clear it
      */
     public native void setAntiAlias(boolean aa);
-    
+
     /**
      * Helper for getFlags(), returning true if DITHER_FLAG bit is set
      * Dithering affects how colors that are higher precision than the device
@@ -611,7 +611,7 @@
     public final boolean isDither() {
         return (getFlags() & DITHER_FLAG) != 0;
     }
-    
+
     /**
      * Helper for setFlags(), setting or clearing the DITHER_FLAG bit
      * Dithering affects how colors that are higher precision than the device
@@ -623,7 +623,7 @@
      * @param dither true to set the dithering bit in flags, false to clear it
      */
     public native void setDither(boolean dither);
-    
+
     /**
      * Helper for getFlags(), returning true if LINEAR_TEXT_FLAG bit is set
      *
@@ -649,7 +649,7 @@
     public final boolean isSubpixelText() {
         return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0;
     }
-    
+
     /**
      * Helper for setFlags(), setting or clearing the SUBPIXEL_TEXT_FLAG bit
      *
@@ -657,7 +657,7 @@
      *                     flags, false to clear it.
      */
     public native void setSubpixelText(boolean subpixelText);
-    
+
     /**
      * Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set
      *
@@ -708,7 +708,7 @@
      *                     flags, false to clear it.
      */
     public native void setFakeBoldText(boolean fakeBoldText);
-    
+
     /**
      * Whether or not the bitmap filter is activated.
      * Filtering affects the sampling of bitmaps when they are transformed.
@@ -720,13 +720,13 @@
     public final boolean isFilterBitmap() {
         return (getFlags() & FILTER_BITMAP_FLAG) != 0;
     }
-    
+
     /**
      * Helper for setFlags(), setting or clearing the FILTER_BITMAP_FLAG bit.
      * Filtering affects the sampling of bitmaps when they are transformed.
      * Filtering does not affect how the colors in the bitmap are converted into
      * device pixels. That is dependent on dithering and xfermodes.
-     * 
+     *
      * @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's
      *               flags, false to clear it.
      */
@@ -773,7 +773,7 @@
      * @param color The new color (including alpha) to set in the paint.
      */
     public native void setColor(int color);
-    
+
     /**
      * Helper to getColor() that just returns the color's alpha value. This is
      * the same as calling getColor() >>> 24. It always returns a value between
@@ -1285,7 +1285,7 @@
      */
     public static class FontMetrics {
         /**
-         * The maximum distance above the baseline for the tallest glyph in 
+         * The maximum distance above the baseline for the tallest glyph in
          * the font at a given text size.
          */
         public float   top;
@@ -1298,7 +1298,7 @@
          */
         public float   descent;
         /**
-         * The maximum distance below the baseline for the lowest glyph in 
+         * The maximum distance below the baseline for the lowest glyph in
          * the font at a given text size.
          */
         public float   bottom;
@@ -1307,7 +1307,7 @@
          */
         public float   leading;
     }
-    
+
     /**
      * Return the font's recommended interline spacing, given the Paint's
      * settings for typeface, textSize, etc. If metrics is not null, return the
@@ -1318,7 +1318,7 @@
      * @return the font's recommended interline spacing.
      */
     public native float getFontMetrics(FontMetrics metrics);
-    
+
     /**
      * Allocates a new FontMetrics object, and then calls getFontMetrics(fm)
      * with it, returning the object.
@@ -1328,7 +1328,7 @@
         getFontMetrics(fm);
         return fm;
     }
-    
+
     /**
      * Convenience method for callers that want to have FontMetrics values as
      * integers.
@@ -1339,7 +1339,7 @@
         public int   descent;
         public int   bottom;
         public int   leading;
-        
+
         @Override public String toString() {
             return "FontMetricsInt: top=" + top + " ascent=" + ascent +
                     " descent=" + descent + " bottom=" + bottom +
@@ -1364,7 +1364,7 @@
         getFontMetricsInt(fm);
         return fm;
     }
-    
+
     /**
      * Return the recommend line spacing based on the current typeface and
      * text size.
@@ -1407,7 +1407,7 @@
     }
 
     private native float native_measureText(char[] text, int index, int count, int bidiFlags);
-    
+
     /**
      * Return the width of the text.
      *
@@ -1439,7 +1439,7 @@
     }
 
     private native float native_measureText(String text, int start, int end, int bidiFlags);
-    
+
     /**
      * Return the width of the text.
      *
@@ -1466,7 +1466,7 @@
     }
 
     private native float native_measureText(String text, int bidiFlags);
-    
+
     /**
      * Return the width of the text.
      *
@@ -1503,7 +1503,7 @@
         TemporaryBuffer.recycle(buf);
         return result;
     }
-    
+
     /**
      * Measure the text, stopping early if the measured width exceeds maxWidth.
      * Return the number of chars that were measured, and if measuredWidth is
@@ -1532,20 +1532,22 @@
             return 0;
         }
         if (!mHasCompatScaling) {
-            return native_breakText(text, index, count, maxWidth, mBidiFlags, measuredWidth);
+            return native_breakText(mNativePaint, mNativeTypeface, text, index, count, maxWidth,
+                    mBidiFlags, measuredWidth);
         }
 
         final float oldSize = getTextSize();
-        setTextSize(oldSize*mCompatScaling);
-        int res = native_breakText(text, index, count, maxWidth*mCompatScaling, mBidiFlags,
-                measuredWidth);
+        setTextSize(oldSize * mCompatScaling);
+        int res = native_breakText(mNativePaint, mNativeTypeface, text, index, count,
+                maxWidth * mCompatScaling, mBidiFlags, measuredWidth);
         setTextSize(oldSize);
         if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
         return res;
     }
 
-    private native int native_breakText(char[] text, int index, int count,
-                                        float maxWidth, int bidiFlags, float[] measuredWidth);
+    private static native int native_breakText(long native_object, long native_typeface,
+                                               char[] text, int index, int count,
+                                               float maxWidth, int bidiFlags, float[] measuredWidth);
 
     /**
      * Measure the text, stopping early if the measured width exceeds maxWidth.
@@ -1622,19 +1624,21 @@
             return 0;
         }
         if (!mHasCompatScaling) {
-            return native_breakText(text, measureForwards, maxWidth, mBidiFlags, measuredWidth);
+            return native_breakText(mNativePaint, mNativeTypeface, text, measureForwards,
+                    maxWidth, mBidiFlags, measuredWidth);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize*mCompatScaling);
-        int res = native_breakText(text, measureForwards, maxWidth*mCompatScaling, mBidiFlags,
-                measuredWidth);
+        int res = native_breakText(mNativePaint, mNativeTypeface, text, measureForwards,
+                maxWidth*mCompatScaling, mBidiFlags, measuredWidth);
         setTextSize(oldSize);
         if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
         return res;
     }
 
-    private native int native_breakText(String text, boolean measureForwards,
+    private static native int native_breakText(long native_object, long native_typeface,
+                                        String text, boolean measureForwards,
                                         float maxWidth, int bidiFlags, float[] measuredWidth);
 
     /**
@@ -1738,7 +1742,7 @@
         if (end - start > widths.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        
+
         if (text.length() == 0 || start == end) {
             return 0;
         }
@@ -1755,7 +1759,7 @@
         }
         return res;
     }
-    
+
     /**
      * Return the advance widths for the characters in the string.
      *
@@ -1769,46 +1773,6 @@
     }
 
     /**
-     * Return the glyph Ids for the characters in the string.
-     *
-     * @param text   The text to measure
-     * @param start  The index of the first char to to measure
-     * @param end    The end of the text slice to measure
-     * @param contextStart the index of the first character to use for shaping context,
-     * must be <= start
-     * @param contextEnd the index past the last character to use for shaping context,
-     * must be >= end
-     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
-     * or {@link #DIRECTION_RTL}
-     * @param glyphs array to receive the glyph Ids of the characters.
-     *               Must be at least a large as the text.
-     * @return       the number of glyphs in the returned array
-     *
-     * @hide
-     *
-     * Used only for BiDi / RTL Tests
-     */
-    public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd,
-            int flags, char[] glyphs) {
-        if (text == null) {
-            throw new IllegalArgumentException("text cannot be null");
-        }
-        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
-            throw new IllegalArgumentException("unknown flags value: " + flags);
-        }
-        if ((start | end | contextStart | contextEnd | (end - start)
-                | (start - contextStart) | (contextEnd - end) | (text.length() - end)
-                | (text.length() - contextEnd)) < 0) {
-            throw new IndexOutOfBoundsException();
-        }
-        if (end - start > glyphs.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-        return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd,
-                flags, glyphs);
-    }
-
-    /**
      * Convenience overload that takes a char array instead of a
      * String.
      *
@@ -1816,15 +1780,12 @@
      * @hide
      */
     public float getTextRunAdvances(char[] chars, int index, int count,
-            int contextIndex, int contextCount, int flags, float[] advances,
+            int contextIndex, int contextCount, boolean isRtl, float[] advances,
             int advancesIndex) {
 
         if (chars == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
-        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
-            throw new IllegalArgumentException("unknown flags value: " + flags);
-        }
         if ((index | count | contextIndex | contextCount | advancesIndex
                 | (index - contextIndex) | (contextCount - count)
                 | ((contextIndex + contextCount) - (index + count))
@@ -1839,13 +1800,13 @@
         }
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count,
-                    contextIndex, contextCount, flags, advances, advancesIndex);
+                    contextIndex, contextCount, isRtl, advances, advancesIndex);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float res = native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count,
-                contextIndex, contextCount, flags, advances, advancesIndex);
+                contextIndex, contextCount, isRtl, advances, advancesIndex);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -1864,7 +1825,7 @@
      * @hide
      */
     public float getTextRunAdvances(CharSequence text, int start, int end,
-            int contextStart, int contextEnd, int flags, float[] advances,
+            int contextStart, int contextEnd, boolean isRtl, float[] advances,
             int advancesIndex) {
 
         if (text == null) {
@@ -1880,16 +1841,16 @@
 
         if (text instanceof String) {
             return getTextRunAdvances((String) text, start, end,
-                    contextStart, contextEnd, flags, advances, advancesIndex);
+                    contextStart, contextEnd, isRtl, advances, advancesIndex);
         }
         if (text instanceof SpannedString ||
             text instanceof SpannableString) {
             return getTextRunAdvances(text.toString(), start, end,
-                    contextStart, contextEnd, flags, advances, advancesIndex);
+                    contextStart, contextEnd, isRtl, advances, advancesIndex);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunAdvances(start, end,
-                    contextStart, contextEnd, flags, advances, advancesIndex, this);
+                    contextStart, contextEnd, isRtl, advances, advancesIndex, this);
         }
         if (text.length() == 0 || end == start) {
             return 0f;
@@ -1900,7 +1861,7 @@
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
         float result = getTextRunAdvances(buf, start - contextStart, len,
-                0, contextLen, flags, advances, advancesIndex);
+                0, contextLen, isRtl, advances, advancesIndex);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -1937,8 +1898,7 @@
      * must be <= start
      * @param contextEnd the index past the last character to use for shaping context,
      * must be >= end
-     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
-     * or {@link #DIRECTION_RTL}
+     * @param isRtl whether the run is in RTL direction
      * @param advances array to receive the advances, must have room for all advances,
      * can be null if only total advance is needed
      * @param advancesIndex the position in advances at which to put the
@@ -1948,14 +1908,11 @@
      * @hide
      */
     public float getTextRunAdvances(String text, int start, int end, int contextStart,
-            int contextEnd, int flags, float[] advances, int advancesIndex) {
+            int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
 
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
-        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
-            throw new IllegalArgumentException("unknown flags value: " + flags);
-        }
         if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
                 | (start - contextStart) | (contextEnd - end)
                 | (text.length() - contextEnd)
@@ -1970,13 +1927,13 @@
 
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end,
-                    contextStart, contextEnd, flags, advances, advancesIndex);
+                    contextStart, contextEnd, isRtl, advances, advancesIndex);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float totalAdvance = native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end,
-                contextStart, contextEnd, flags, advances, advancesIndex);
+                contextStart, contextEnd, isRtl, advances, advancesIndex);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -2005,7 +1962,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextLength the length of the context
-     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -2014,7 +1971,7 @@
      * @hide
      */
     public int getTextRunCursor(char[] text, int contextStart, int contextLength,
-            int flags, int offset, int cursorOpt) {
+            int dir, int offset, int cursorOpt) {
         int contextEnd = contextStart + contextLength;
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
@@ -2024,7 +1981,7 @@
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextLength, flags, offset, cursorOpt);
+                contextStart, contextLength, dir, offset, cursorOpt);
     }
 
     /**
@@ -2045,7 +2002,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
-     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -2054,22 +2011,22 @@
      * @hide
      */
     public int getTextRunCursor(CharSequence text, int contextStart,
-           int contextEnd, int flags, int offset, int cursorOpt) {
+           int contextEnd, int dir, int offset, int cursorOpt) {
 
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             return getTextRunCursor(text.toString(), contextStart, contextEnd,
-                    flags, offset, cursorOpt);
+                    dir, offset, cursorOpt);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunCursor(
-                    contextStart, contextEnd, flags, offset, cursorOpt, this);
+                    contextStart, contextEnd, dir, offset, cursorOpt, this);
         }
 
         int contextLen = contextEnd - contextStart;
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
-        int result = getTextRunCursor(buf, 0, contextLen, flags, offset - contextStart, cursorOpt);
+        int result = getTextRunCursor(buf, 0, contextLen, dir, offset - contextStart, cursorOpt);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -2092,7 +2049,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
-     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -2101,7 +2058,7 @@
      * @hide
      */
     public int getTextRunCursor(String text, int contextStart, int contextEnd,
-            int flags, int offset, int cursorOpt) {
+            int dir, int offset, int cursorOpt) {
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
                 | (text.length() - contextEnd) | cursorOpt) < 0)
@@ -2110,7 +2067,7 @@
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextEnd, flags, offset, cursorOpt);
+                contextStart, contextEnd, dir, offset, cursorOpt);
     }
 
     /**
@@ -2156,7 +2113,7 @@
         native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y,
                 path.ni());
     }
-    
+
     /**
      * Return in bounds (allocated by the caller) the smallest rectangle that
      * encloses all of the characters, with an implied origin at (0,0).
@@ -2176,7 +2133,7 @@
         }
         nativeGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds);
     }
-    
+
     /**
      * Return in bounds (allocated by the caller) the smallest rectangle that
      * encloses all of the characters, with an implied origin at (0,0).
@@ -2197,7 +2154,7 @@
         nativeGetCharArrayBounds(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags,
             bounds);
     }
-    
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -2252,15 +2209,15 @@
 
     private static native float native_getTextRunAdvances(long native_object, long native_typeface,
             char[] text, int index, int count, int contextIndex, int contextCount,
-            int flags, float[] advances, int advancesIndex);
+            boolean isRtl, float[] advances, int advancesIndex);
     private static native float native_getTextRunAdvances(long native_object, long native_typeface,
             String text, int start, int end, int contextStart, int contextEnd,
-            int flags, float[] advances, int advancesIndex);
+            boolean isRtl, float[] advances, int advancesIndex);
 
     private native int native_getTextRunCursor(long native_object, char[] text,
-            int contextStart, int contextLength, int flags, int offset, int cursorOpt);
+            int contextStart, int contextLength, int dir, int offset, int cursorOpt);
     private native int native_getTextRunCursor(long native_object, String text,
-            int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
+            int contextStart, int contextEnd, int dir, int offset, int cursorOpt);
 
     private static native void native_getTextPath(long native_object, long native_typeface,
             int bidiFlags, char[] text, int index, int count, float x, float y, long path);
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c600f47..c40a66d 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -62,7 +62,7 @@
         }
         mNativePath = init2(valNative);
     }
-    
+
     /**
      * Clear any lines and curves from the path, making it empty.
      * This does NOT change the fill-type setting.
@@ -205,7 +205,7 @@
          * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
          */
         INVERSE_EVEN_ODD(3);
-        
+
         FillType(int ni) {
             nativeInt = ni;
         }
@@ -425,7 +425,7 @@
      * the path is different from the path's current last point, then an
      * automatic lineTo() is added to connect the current contour to the
      * start of the arc. However, if the path is empty, then we call moveTo()
-     * with the first point of the arc. The sweep angle is tread mod 360.
+     * with the first point of the arc.
      *
      * @param oval        The bounds of oval defining shape and size of the arc
      * @param startAngle  Starting angle (in degrees) where the arc begins
@@ -435,10 +435,9 @@
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle,
                       boolean forceMoveTo) {
-        isSimplePath = false;
-        native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo);
+        arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
     }
-    
+
     /**
      * Append the specified arc to the path as a new contour. If the start of
      * the path is different from the path's current last point, then an
@@ -451,10 +450,27 @@
      * @param sweepAngle  Sweep angle (in degrees) measured clockwise
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle) {
-        isSimplePath = false;
-        native_arcTo(mNativePath, oval, startAngle, sweepAngle, false);
+        arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
     }
-    
+
+    /**
+     * Append the specified arc to the path as a new contour. If the start of
+     * the path is different from the path's current last point, then an
+     * automatic lineTo() is added to connect the current contour to the
+     * start of the arc. However, if the path is empty, then we call moveTo()
+     * with the first point of the arc.
+     *
+     * @param startAngle  Starting angle (in degrees) where the arc begins
+     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
+     *                    mod 360.
+     * @param forceMoveTo If true, always begin a new contour with the arc
+     */
+    public void arcTo(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle, boolean forceMoveTo) {
+        isSimplePath = false;
+        native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
+    }
+
     /**
      * Close the current contour. If the current point is not equal to the
      * first point of the contour, a line segment is automatically added.
@@ -473,13 +489,13 @@
         CW  (1),    // must match enum in SkPath.h
         /** counter-clockwise */
         CCW (2);    // must match enum in SkPath.h
-        
+
         Direction(int ni) {
             nativeInt = ni;
         }
         final int nativeInt;
     }
-    
+
     private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) {
         if (mLastDirection == null) {
             mLastDirection = dir;
@@ -557,11 +573,19 @@
      * @param sweepAngle Sweep angle (in degrees) measured clockwise
      */
     public void addArc(RectF oval, float startAngle, float sweepAngle) {
-        if (oval == null) {
-            throw new NullPointerException("need oval parameter");
-        }
+        addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
+    }
+
+    /**
+     * Add the specified arc to the path as a new contour.
+     *
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     */
+    public void addArc(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle) {
         isSimplePath = false;
-        native_addArc(mNativePath, oval, startAngle, sweepAngle);
+        native_addArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle);
     }
 
     /**
@@ -573,13 +597,22 @@
      * @param dir  The direction to wind the round-rectangle's contour
      */
     public void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
-        if (rect == null) {
-            throw new NullPointerException("need rect parameter");
-        }
-        isSimplePath = false;
-        native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt);
+        addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
     }
-    
+
+    /**
+     * Add a closed round-rectangle contour to the path
+     *
+     * @param rx   The x-radius of the rounded corners on the round-rectangle
+     * @param ry   The y-radius of the rounded corners on the round-rectangle
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+            Direction dir) {
+        isSimplePath = false;
+        native_addRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt);
+    }
+
     /**
      * Add a closed round-rectangle contour to the path. Each corner receives
      * two radius values [X, Y]. The corners are ordered top-left, top-right,
@@ -593,13 +626,26 @@
         if (rect == null) {
             throw new NullPointerException("need rect parameter");
         }
+        addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
+    }
+
+    /**
+     * Add a closed round-rectangle contour to the path. Each corner receives
+     * two radius values [X, Y]. The corners are ordered top-left, top-right,
+     * bottom-right, bottom-left
+     *
+     * @param radii Array of 8 values, 4 pairs of [X,Y] radii
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
+            Direction dir) {
         if (radii.length < 8) {
             throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
         }
         isSimplePath = false;
-        native_addRoundRect(mNativePath, rect, radii, dir.nativeInt);
+        native_addRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
     }
-    
+
     /**
      * Add a copy of src to the path, offset by (dx,dy)
      *
@@ -755,19 +801,24 @@
                                         float x2, float y2, float x3, float y3);
     private static native void native_rCubicTo(long nPath, float x1, float y1,
                                         float x2, float y2, float x3, float y3);
-    private static native void native_arcTo(long nPath, RectF oval,
-                    float startAngle, float sweepAngle, boolean forceMoveTo);
+    private static native void native_arcTo(long nPath, float left, float top,
+                                            float right, float bottom, float startAngle,
+                                            float sweepAngle, boolean forceMoveTo);
     private static native void native_close(long nPath);
     private static native void native_addRect(long nPath, float left, float top,
                                             float right, float bottom, int dir);
     private static native void native_addOval(long nPath, float left, float top,
             float right, float bottom, int dir);
     private static native void native_addCircle(long nPath, float x, float y, float radius, int dir);
-    private static native void native_addArc(long nPath, RectF oval,
-                                            float startAngle, float sweepAngle);
-    private static native void native_addRoundRect(long nPath, RectF rect,
+    private static native void native_addArc(long nPath, float left, float top,
+                                             float right, float bottom,
+                                             float startAngle, float sweepAngle);
+    private static native void native_addRoundRect(long nPath, float left, float top,
+                                                   float right, float bottom,
                                                    float rx, float ry, int dir);
-    private static native void native_addRoundRect(long nPath, RectF r, float[] radii, int dir);
+    private static native void native_addRoundRect(long nPath, float left, float top,
+                                                   float right, float bottom,
+                                                   float[] radii, int dir);
     private static native void native_addPath(long nPath, long src, float dx, float dy);
     private static native void native_addPath(long nPath, long src);
     private static native void native_addPath(long nPath, long src, long matrix);
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index a16c099..5aa7c6a 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -31,18 +31,13 @@
     private Canvas mRecordingCanvas;
     private final long mNativePicture;
 
-    /**
-     * @hide
-     */
-    public final boolean createdFromStream;
-
     private static final int WORKING_STREAM_STORAGE = 16 * 1024;
 
     /**
      * Creates an empty picture that is ready to record.
      */
     public Picture() {
-        this(nativeConstructor(0), false);
+        this(nativeConstructor(0));
     }
 
     /**
@@ -51,9 +46,25 @@
      * changes will not be reflected in this picture.
      */
     public Picture(Picture src) {
-        this(nativeConstructor(src != null ? src.mNativePicture : 0), false);
+        this(nativeConstructor(src != null ? src.mNativePicture : 0));
     }
-    
+
+    private Picture(long nativePicture) {
+        if (nativePicture == 0) {
+            throw new RuntimeException();
+        }
+        mNativePicture = nativePicture;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor(mNativePicture);
+        } finally {
+            super.finalize();
+        }
+    }
+
     /**
      * 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 +78,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
@@ -85,29 +96,36 @@
      * Get the width of the picture as passed to beginRecording. This
      * does not reflect (per se) the content of the picture.
      */
-    public native int getWidth();
+    public int getWidth() {
+      return nativeGetWidth(mNativePicture);
+    }
 
     /**
      * Get the height of the picture as passed to beginRecording. This
      * does not reflect (per se) the content of the picture.
      */
-    public native int getHeight();
-    
+    public int getHeight() {
+      return nativeGetHeight(mNativePicture);
+    }
+
     /**
-     * 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 +137,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
@@ -127,7 +145,7 @@
      */
     @Deprecated
     public static Picture createFromStream(InputStream stream) {
-        return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]), true);
+        return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
     }
 
     /**
@@ -156,38 +174,18 @@
         }
     }
 
-    protected void finalize() throws Throwable {
-        try {
-            nativeDestructor(mNativePicture);
-        } finally {
-            super.finalize();
-        }
-    }
-
-    final long ni() {
-        return mNativePicture;
-    }
-    
-    private Picture(long nativePicture, boolean fromStream) {
-        if (nativePicture == 0) {
-            throw new RuntimeException();
-        }
-        mNativePicture = nativePicture;
-        createdFromStream = fromStream;
-    }
-
     // return empty picture if src is 0, or a copy of the native src
     private static native long nativeConstructor(long nativeSrcOr0);
-    private static native long nativeCreateFromStream(InputStream stream,
-                                                byte[] storage);
-    private static native long nativeBeginRecording(long nativeCanvas,
-                                                    int w, int h);
+    private static native long nativeCreateFromStream(InputStream stream, byte[] storage);
+    private static native int nativeGetWidth(long nativePicture);
+    private static native int nativeGetHeight(long nativePicture);
+    private static native long nativeBeginRecording(long nativeCanvas, int w, int h);
     private static native void nativeEndRecording(long nativeCanvas);
     private static native void nativeDraw(long nativeCanvas, long nativePicture);
     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,21 +193,18 @@
             super(nativeCanvas);
             mPicture = pict;
         }
-        
+
         @Override
         public void setBitmap(Bitmap bitmap) {
-            throw new RuntimeException(
-                                "Cannot call setBitmap on a picture canvas");
+            throw new RuntimeException("Cannot call setBitmap on a picture canvas");
         }
 
         @Override
         public void drawPicture(Picture picture) {
             if (mPicture == picture) {
-                throw new RuntimeException(
-                            "Cannot draw a picture into its recording canvas");
+                throw new RuntimeException("Cannot draw a picture into its recording canvas");
             }
             super.drawPicture(picture);
         }
     }
 }
-
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 0862cdd..17795f3 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -126,7 +126,34 @@
      */
     public SurfaceTexture(int texName, boolean singleBufferMode) {
         mCreatorLooper = Looper.myLooper();
-        nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+        nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+    }
+
+    /**
+     * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+     *
+     * In single buffered mode the application is responsible for serializing access to the image
+     * content buffer. Each time the image content is to be updated, the
+     * {@link #releaseTexImage()} method must be called before the image content producer takes
+     * ownership of the buffer. For example, when producing image content with the NDK
+     * ANativeWindow_lock and ANativeWindow_unlockAndPost functions, {@link #releaseTexImage()}
+     * must be called before each ANativeWindow_lock, or that call will fail. When producing
+     * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
+     * OpenGL ES function call each frame.
+     *
+     * Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
+     * this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
+     * in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
+     * image content using OpenGL ES.
+     *
+     * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode.
+     *
+     * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
+     * @hide
+     */
+    public SurfaceTexture(boolean singleBufferMode) {
+        mCreatorLooper = Looper.myLooper();
+        nativeInit(true, 0, singleBufferMode, new WeakReference<SurfaceTexture>(this));
     }
 
     /**
@@ -339,8 +366,8 @@
         }
     }
 
-    private native void nativeInit(int texName, boolean singleBufferMode,
-            WeakReference<SurfaceTexture> weakSelf)
+    private native void nativeInit(boolean isDetached, int texName,
+            boolean singleBufferMode, WeakReference<SurfaceTexture> weakSelf)
             throws Surface.OutOfResourcesException;
     private native void nativeFinalize();
     private native void nativeGetTransformMatrix(float[] mtx);
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index cb48de2..0dc903a 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -117,7 +117,7 @@
         if (sSystemFontMap != null) {
             return create(sSystemFontMap.get(familyName), style);
         }
-        return new Typeface(nativeCreate(familyName, style));
+        return null;
     }
 
     /**
@@ -183,11 +183,9 @@
             if (fontFamily.addFontFromAsset(mgr, path)) {
                 FontFamily[] families = { fontFamily };
                 return createFromFamiliesWithDefault(families);
-            } else {
-                return null;
             }
         }
-        return new Typeface(nativeCreateFromAsset(mgr, path));
+        return null;
     }
 
     /**
@@ -212,11 +210,9 @@
             if (fontFamily.addFont(path)) {
                 FontFamily[] families = { fontFamily };
                 return createFromFamiliesWithDefault(families);
-            } else {
-                return null;
             }
         }
-        return new Typeface(nativeCreateFromFile(path));
+        return null;
     }
 
     /**
@@ -383,12 +379,9 @@
         return result;
     }
 
-    private static native long nativeCreate(String familyName, int style);
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native void nativeUnref(long native_instance);
     private static native int  nativeGetStyle(long native_instance);
-    private static native long nativeCreateFromAsset(AssetManager mgr, String path);
-    private static native long nativeCreateFromFile(String path);
     private static native long nativeCreateFromArray(long[] familyArray);
     private static native void nativeSetDefault(long native_instance);
 }
diff --git a/graphics/java/android/graphics/drawable/Animatable.java b/graphics/java/android/graphics/drawable/Animatable.java
index 9dc62c3..4edfad4 100644
--- a/graphics/java/android/graphics/drawable/Animatable.java
+++ b/graphics/java/android/graphics/drawable/Animatable.java
@@ -32,7 +32,7 @@
 
     /**
      * Indicates whether the animation is running.
-     * 
+     *
      * @return True if the animation is running, false otherwise.
      */
     boolean isRunning();
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index e8024f7..0fd4423 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -19,6 +19,8 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.ColorFilter;
+import android.graphics.PorterDuff.Mode;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
@@ -88,6 +90,7 @@
         canvas.restoreToCount(saveCount);
     }
 
+    @Override
     public void start() {
         if (!mRunning) {
             mRunning = true;
@@ -95,11 +98,13 @@
         }
     }
 
+    @Override
     public void stop() {
         mRunning = false;
         unscheduleSelf(this);
     }
 
+    @Override
     public boolean isRunning() {
         return mRunning;
     }
@@ -108,10 +113,11 @@
         unscheduleSelf(this);
         scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration);
     }
-    
+
+    @Override
     public void run() {
         // TODO: This should be computed in draw(Canvas), based on the amount
-        // of time since the last frame drawn 
+        // of time since the last frame drawn
         mCurrentDegrees += mIncrement;
         if (mCurrentDegrees > (360.0f - mIncrement)) {
             mCurrentDegrees = 0.0f;
@@ -119,7 +125,7 @@
         invalidateSelf();
         nextFrame();
     }
-    
+
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
         mState.mDrawable.setVisible(visible, restart);
@@ -133,8 +139,8 @@
             unscheduleSelf(this);
         }
         return changed;
-    }    
-    
+    }
+
     /**
      * Returns the drawable rotated by this RotateDrawable.
      */
@@ -148,7 +154,7 @@
                 | mState.mChangingConfigurations
                 | mState.mDrawable.getChangingConfigurations();
     }
-    
+
     @Override
     public void setAlpha(int alpha) {
         mState.mDrawable.setAlpha(alpha);
@@ -165,10 +171,16 @@
     }
 
     @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mState.mDrawable.setTint(tint, tintMode);
+    }
+
+    @Override
     public int getOpacity() {
         return mState.mDrawable.getOpacity();
     }
 
+    @Override
     public void invalidateDrawable(Drawable who) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -176,6 +188,7 @@
         }
     }
 
+    @Override
     public void scheduleDrawable(Drawable who, Runnable what, long when) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -183,6 +196,7 @@
         }
     }
 
+    @Override
     public void unscheduleDrawable(Drawable who, Runnable what) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -194,7 +208,7 @@
     public boolean getPadding(Rect padding) {
         return mState.mDrawable.getPadding(padding);
     }
-    
+
     @Override
     public boolean isStateful() {
         return mState.mDrawable.isStateful();
@@ -206,6 +220,16 @@
     }
 
     @Override
+    protected boolean onLevelChange(int level) {
+        return mState.mDrawable.setLevel(level);
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        return mState.mDrawable.setState(state);
+    }
+
+    @Override
     public int getIntrinsicWidth() {
         return mState.mDrawable.getIntrinsicWidth();
     }
@@ -231,11 +255,11 @@
         final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable);
 
         super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
-        
+
         TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
         final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION;
         final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-        
+
         tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
         final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
         final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
@@ -250,7 +274,7 @@
         }
 
         a.recycle();
-        
+
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
@@ -306,7 +330,7 @@
         Drawable mDrawable;
 
         int mChangingConfigurations;
-        
+
         boolean mPivotXRel;
         float mPivotX;
         boolean mPivotYRel;
@@ -315,7 +339,7 @@
         int mFramesCount;
 
         private boolean mCanConstantState;
-        private boolean mCheckedConstantState;        
+        private boolean mCheckedConstantState;
 
         public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner,
                 Resources res) {
@@ -341,12 +365,12 @@
         public Drawable newDrawable() {
             return new AnimatedRotateDrawable(this, null);
         }
-        
+
         @Override
         public Drawable newDrawable(Resources res) {
             return new AnimatedRotateDrawable(this, res);
         }
-        
+
         @Override
         public int getChangingConfigurations() {
             return mChangingConfigurations;
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index a5a074c..e1dec9d 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -537,4 +537,3 @@
         }
     }
 }
-
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
new file mode 100644
index 0000000..c787fb0
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -0,0 +1,331 @@
+/*
+ * 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;
+
+/**
+ * This class uses {@link android.animation.ObjectAnimator} and
+ * {@link android.animation.AnimatorSet} to animate the properties of a
+ * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
+ * <p>
+ * AnimatedVectorDrawable are normally defined as 3 separate XML files.
+ * </p>
+ * <p>
+ * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
+ * Note that we allow the animation happen on the group's attributes and path's
+ * attributes, which requires they are uniquely named in this xml file. Groups
+ * and paths without animations do not need names.
+ * </p>
+ * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
+ * <pre>
+ * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot; &gt;
+ *     &lt;size
+ *         android:height=&quot;64dp&quot;
+ *         android:width=&quot;64dp&quot; /&gt;
+ *     &lt;viewport
+ *         android:viewportHeight=&quot;600&quot;
+ *         android:viewportWidth=&quot;600&quot; /&gt;
+ *     &lt;group
+ *         android:name=&quot;rotationGroup&quot;
+ *         android:pivotX=&quot;300.0&quot;
+ *         android:pivotY=&quot;300.0&quot;
+ *         android:rotation=&quot;45.0&quot; &gt;
+ *         &lt;path
+ *             android:name=&quot;v&quot;
+ *             android:fill=&quot;#000000&quot;
+ *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
+ *     &lt;/group&gt;
+ * &lt;/vector&gt;
+ * </pre></li>
+ * <p>
+ * Second is the AnimatedVectorDrawable's xml file, which defines the target
+ * VectorDrawable, the target paths and groups to animate, the properties of the
+ * path and group to animate and the animations defined as the ObjectAnimators
+ * or AnimatorSets.
+ * </p>
+ * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
+ * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
+ * <pre>
+ * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+ *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
+ *     &lt;target
+ *         android:name=&quot;rotationGroup&quot;
+ *         android:animation=&quot;@anim/rotation&quot; /&gt;
+ *     &lt;target
+ *         android:name=&quot;v&quot;
+ *         android:animation=&quot;@anim/path_morph&quot; /&gt;
+ * &lt;/animated-vector&gt;
+ * </pre></li>
+ * <p>
+ * Last is the Animator xml file, which is the same as a normal ObjectAnimator
+ * or AnimatorSet.
+ * To complete this example, here are the 2 animator files used in avd.xml:
+ * rotation.xml and path_morph.xml.
+ * </p>
+ * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
+ * <pre>
+ * &lt;objectAnimator
+ *     android:duration=&quot;6000&quot;
+ *     android:propertyName=&quot;rotation&quot;
+ *     android:valueFrom=&quot;0&quot;
+ *     android:valueTo=&quot;360&quot; /&gt;
+ * </pre></li>
+ * <li>Here is the path_morph.xml, which will morph the path from one shape to
+ * the other. Note that the paths must be compatible for morphing.
+ * In more details, the paths should have exact same length of commands , and
+ * exact same length of parameters for each commands.
+ * Note that the path string are better stored in strings.xml for reusing.
+ * <pre>
+ * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
+ *     &lt;objectAnimator
+ *         android:duration=&quot;3000&quot;
+ *         android:propertyName=&quot;pathData&quot;
+ *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
+ *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
+ *         android:valueType=&quot;pathType&quot;/&gt;
+ * &lt;/set&gt;
+ * </pre></li>
+ *
+ * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
+ * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
+ * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
+ */
+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/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 16548d0..cef3377 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -28,7 +28,6 @@
 import android.util.AttributeSet;
 
 /**
- * 
  * An object used to create frame-by-frame animations, defined by a series of Drawable objects,
  * which can be used as a View object's background.
  * <p>
@@ -81,20 +80,41 @@
  */
 public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
     private final AnimationState mAnimationState;
+
+    /** The current frame, may be -1 when not animating. */
     private int mCurFrame = -1;
+
+    /** Whether the drawable has an animation callback posted. */
+    private boolean mRunning;
+
+    /** Whether the drawable should animate when visible. */
     private boolean mAnimating;
+
     private boolean mMutated;
 
     public AnimationDrawable() {
         this(null, null);
     }
 
+    /**
+     * Sets whether this AnimationDrawable is visible.
+     * <p>
+     * When the drawable becomes invisible, it will pause its animation. A
+     * subsequent change to visible with <code>restart</code> set to true will
+     * restart the animation from the first frame. If <code>restart</code> is
+     * false, the animation will resume from the most recent frame.
+     *
+     * @param visible true if visible, false otherwise
+     * @param restart when visible, true to force the animation to restart
+     *                from the first frame
+     * @return true if the new visibility is different than its previous state
+     */
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
-        boolean changed = super.setVisible(visible, restart);
+        final boolean changed = super.setVisible(visible, restart);
         if (visible) {
-            if (changed || restart) {
-                setFrame(0, true, restart || mCurFrame >= 0);
+            if (restart || changed) {
+                setFrame(restart ? 0 : mCurFrame, true, mAnimating);
             }
         } else {
             unscheduleSelf(this);
@@ -114,7 +134,10 @@
      * @see #isRunning()
      * @see #stop()
      */
+    @Override
     public void start() {
+        mAnimating = true;
+
         if (!isRunning()) {
             run();
         }
@@ -127,7 +150,10 @@
      * @see #isRunning()
      * @see #start()
      */
+    @Override
     public void stop() {
+        mAnimating = false;
+
         if (isRunning()) {
             unscheduleSelf(this);
         }
@@ -138,8 +164,9 @@
      *
      * @return true if the animation is running, false otherwise
      */
+    @Override
     public boolean isRunning() {
-        return mAnimating;
+        return mRunning;
     }
 
     /**
@@ -148,6 +175,7 @@
      *
      * @see #start()
      */
+    @Override
     public void run() {
         nextFrame(false);
     }
@@ -155,7 +183,7 @@
     @Override
     public void unscheduleSelf(Runnable what) {
         mCurFrame = -1;
-        mAnimating = false;
+        mRunning = false;
         super.unscheduleSelf(what);
     }
 
@@ -165,41 +193,41 @@
     public int getNumberOfFrames() {
         return mAnimationState.getChildCount();
     }
-    
+
     /**
      * @return The Drawable at the specified frame index
      */
     public Drawable getFrame(int index) {
         return mAnimationState.getChild(index);
     }
-    
+
     /**
-     * @return The duration in milliseconds of the frame at the 
+     * @return The duration in milliseconds of the frame at the
      * specified index
      */
     public int getDuration(int i) {
         return mAnimationState.mDurations[i];
     }
-    
+
     /**
      * @return True of the animation will play once, false otherwise
      */
     public boolean isOneShot() {
         return mAnimationState.mOneShot;
     }
-    
+
     /**
      * Sets whether the animation should play once or repeat.
-     * 
+     *
      * @param oneShot Pass true if the animation should only play once
      */
     public void setOneShot(boolean oneShot) {
         mAnimationState.mOneShot = oneShot;
     }
-    
+
     /**
      * Add a frame to the animation
-     * 
+     *
      * @param frame The frame to add
      * @param duration How long in milliseconds the frame should appear
      */
@@ -209,13 +237,14 @@
             setFrame(0, true, false);
         }
     }
-    
+
     private void nextFrame(boolean unschedule) {
         int next = mCurFrame+1;
         final int N = mAnimationState.getChildCount();
         if (next >= N) {
             next = 0;
         }
+
         setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1));
     }
 
@@ -223,6 +252,7 @@
         if (frame >= mAnimationState.getChildCount()) {
             return;
         }
+        mAnimating = animate;
         mCurFrame = frame;
         selectDrawable(frame);
         if (unschedule || animate) {
@@ -231,7 +261,7 @@
         if (animate) {
             // Unscheduling may have clobbered these values; restore them
             mCurFrame = frame;
-            mAnimating = true;
+            mRunning = true;
             scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
         }
     }
@@ -239,21 +269,21 @@
     @Override
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
             throws XmlPullParserException, IOException {
-        
+
         TypedArray a = r.obtainAttributes(attrs,
                 com.android.internal.R.styleable.AnimationDrawable);
 
         super.inflateWithAttributes(r, parser, a,
                 com.android.internal.R.styleable.AnimationDrawable_visible);
-        
+
         mAnimationState.setVariablePadding(a.getBoolean(
                 com.android.internal.R.styleable.AnimationDrawable_variablePadding, false));
-            
+
         mAnimationState.mOneShot = a.getBoolean(
                 com.android.internal.R.styleable.AnimationDrawable_oneshot, false);
-        
+
         a.recycle();
-        
+
         int type;
 
         final int innerDepth = parser.getDepth()+1;
@@ -267,7 +297,7 @@
             if (depth > innerDepth || !parser.getName().equals("item")) {
                 continue;
             }
-            
+
             a = r.obtainAttributes(attrs, com.android.internal.R.styleable.AnimationDrawableItem);
             int duration = a.getInt(
                     com.android.internal.R.styleable.AnimationDrawableItem_duration, -1);
@@ -278,9 +308,9 @@
             }
             int drawableRes = a.getResourceId(
                     com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0);
-            
+
             a.recycle();
-            
+
             Drawable dr;
             if (drawableRes != 0) {
                 dr = r.getDrawable(drawableRes);
@@ -295,7 +325,7 @@
                 }
                 dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
             }
-            
+
             mAnimationState.addFrame(dr, duration);
             if (dr != null) {
                 dr.setCallback(this);
@@ -342,7 +372,7 @@
         }
 
         public void addFrame(Drawable dr, int dur) {
-            // Do not combine the following. The array index must be evaluated before 
+            // Do not combine the following. The array index must be evaluated before
             // the array is accessed because super.addChild(dr) has a side effect on mDurations.
             int pos = super.addChild(dr);
             mDurations[pos] = dur;
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index ef6c085..e080375 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -31,6 +31,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
+import android.graphics.drawable.ColorDrawable.ColorState;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.Shader;
@@ -618,9 +619,11 @@
 
     @Override
     public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) {
-        mBitmapState.mTint = tint;
-        mBitmapState.mTintMode = tintMode;
-        computeTintFilter();
+        final BitmapState state = mBitmapState;
+        state.mTint = tint;
+        state.mTintMode = tintMode;
+
+        mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
         invalidateSelf();
     }
 
@@ -638,21 +641,6 @@
         return mBitmapState.mTintMode;
     }
 
-    private void computeTintFilter() {
-        final BitmapState state = mBitmapState;
-        if (state.mTint != null && state.mTintMode != null) {
-            final int color = state.mTint.getColorForState(getState(), 0);
-            if (mTintFilter != null) {
-                mTintFilter.setColor(color);
-                mTintFilter.setMode(state.mTintMode);
-            } else {
-                mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
-            }
-        } else {
-            mTintFilter = null;
-        }
-    }
-
     /**
      * @hide Candidate for future API inclusion
      */
@@ -679,17 +667,11 @@
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
-        final ColorStateList tint = mBitmapState.mTint;
-        if (tint != null) {
-            final int newColor = tint.getColorForState(stateSet, 0);
-            final int oldColor = mTintFilter.getColor();
-            if (oldColor != newColor) {
-                mTintFilter.setColor(newColor);
-                invalidateSelf();
-                return true;
-            }
+        final BitmapState state = mBitmapState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
         }
-
         return false;
     }
 
@@ -775,7 +757,18 @@
 
         final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
         if (tileMode != TILE_MODE_UNDEFINED) {
-            setTileModeInternal(tileMode);
+            final Shader.TileMode mode = parseTileMode(tileMode);
+            setTileModeXY(mode, mode);
+        }
+
+        final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
+        if (tileModeX != TILE_MODE_UNDEFINED) {
+            setTileModeX(parseTileMode(tileModeX));
+        }
+
+        final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
+        if (tileModeY != TILE_MODE_UNDEFINED) {
+            setTileModeY(parseTileMode(tileModeY));
         }
 
         // Update local properties.
@@ -801,20 +794,16 @@
         }
     }
 
-    private void setTileModeInternal(final int tileMode) {
+    private static Shader.TileMode parseTileMode(int tileMode) {
         switch (tileMode) {
-            case TILE_MODE_DISABLED:
-                setTileModeXY(null, null);
-                break;
             case TILE_MODE_CLAMP:
-                setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-                break;
+                return Shader.TileMode.CLAMP;
             case TILE_MODE_REPEAT:
-                setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
-                break;
+                return Shader.TileMode.REPEAT;
             case TILE_MODE_MIRROR:
-                setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
-                break;
+                return Shader.TileMode.MIRROR;
+            default:
+                return null;
         }
     }
 
@@ -937,7 +926,9 @@
     }
 
     /**
-     * Initializes local dynamic properties from state.
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
      */
     private void initializeWithState(BitmapState state, Resources res) {
         if (res != null) {
@@ -946,7 +937,7 @@
             mTargetDensity = state.mTargetDensity;
         }
 
-        computeTintFilter();
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
         computeBitmapSize();
     }
 }
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 3ac9972..174de3a 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -19,10 +19,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
 import android.graphics.*;
+import android.graphics.PorterDuff.Mode;
 import android.view.Gravity;
 import android.util.AttributeSet;
 
@@ -52,7 +54,7 @@
 
     public static final int HORIZONTAL = 1;
     public static final int VERTICAL = 2;
-    
+
     ClipDrawable() {
         this(null, null);
     }
@@ -111,6 +113,7 @@
 
     // overrides from Drawable.Callback
 
+    @Override
     public void invalidateDrawable(Drawable who) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -118,6 +121,7 @@
         }
     }
 
+    @Override
     public void scheduleDrawable(Drawable who, Runnable what, long when) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -125,6 +129,7 @@
         }
     }
 
+    @Override
     public void unscheduleDrawable(Drawable who, Runnable what) {
         final Callback callback = getCallback();
         if (callback != null) {
@@ -169,6 +174,11 @@
     }
 
     @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mClipState.mDrawable.setTint(tint, tintMode);
+    }
+
+    @Override
     public int getOpacity() {
         return mClipState.mDrawable.getOpacity();
     }
@@ -197,7 +207,7 @@
 
     @Override
     public void draw(Canvas canvas) {
-        
+
         if (mClipState.mDrawable.getLevel() == 0) {
             return;
         }
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index df5ca33..3716182 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -17,6 +17,8 @@
 package android.graphics.drawable;
 
 import android.graphics.*;
+import android.graphics.PorterDuff.Mode;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
@@ -39,9 +41,13 @@
  * @attr ref android.R.styleable#ColorDrawable_color
  */
 public class ColorDrawable extends Drawable {
+    private final Paint mPaint = new Paint();
+
     @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
     private ColorState mColorState;
-    private final Paint mPaint = new Paint();
+    private ColorStateList mTint;
+    private PorterDuffColorFilter mTintFilter;
+
     private boolean mMutated;
 
     /**
@@ -84,9 +90,17 @@
 
     @Override
     public void draw(Canvas canvas) {
-        if ((mColorState.mUseColor >>> 24) != 0) {
+        final ColorFilter colorFilter = mPaint.getColorFilter();
+        if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
+            if (colorFilter == null) {
+                mPaint.setColorFilter(mTintFilter);
+            }
+
             mPaint.setColor(mColorState.mUseColor);
             canvas.drawRect(getBounds(), mPaint);
+
+            // Restore original color filter.
+            mPaint.setColorFilter(colorFilter);
         }
     }
 
@@ -141,16 +155,51 @@
     }
 
     /**
-     * Setting a color filter on a ColorDrawable has no effect.
+     * Sets the color filter applied to this color.
+     * <p>
+     * Only supported on version {@link android.os.Build.VERSION_CODES#L} and
+     * above. Calling this method has no effect on earlier versions.
      *
-     * @param colorFilter Ignore.
+     * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter)
      */
     @Override
     public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        final ColorState state = mColorState;
+        if (state.mTint != tint || state.mTintMode != tintMode) {
+            state.mTint = tint;
+            state.mTintMode = tintMode;
+
+            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final ColorState state = mColorState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mTint != null && mTint.isStateful();
     }
 
     @Override
     public int getOpacity() {
+        if (mTintFilter != null || mPaint.getColorFilter() != null) {
+            return PixelFormat.TRANSLUCENT;
+        }
+
         switch (mColorState.mUseColor >>> 24) {
             case 255:
                 return PixelFormat.OPAQUE;
@@ -165,8 +214,7 @@
             throws XmlPullParserException, IOException {
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(
-                r, theme, attrs, R.styleable.ColorDrawable);
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable);
         inflateStateFromTypedArray(a);
         a.recycle();
     }
@@ -225,21 +273,25 @@
     }
 
     final static class ColorState extends ConstantState {
+        int[] mThemeAttrs;
         int mBaseColor; // base color, independent of setAlpha()
         @ViewDebug.ExportedProperty
         int mUseColor;  // basecolor modulated by setAlpha()
         int mChangingConfigurations;
-        int[] mThemeAttrs;
+        ColorStateList mTint;
+        Mode mTintMode;
 
         ColorState() {
             // Empty constructor.
         }
 
         ColorState(ColorState state) {
+            mThemeAttrs = state.mThemeAttrs;
             mBaseColor = state.mBaseColor;
             mUseColor = state.mUseColor;
             mChangingConfigurations = state.mChangingConfigurations;
-            mThemeAttrs = state.mThemeAttrs;
+            mTint = state.mTint;
+            mTintMode = state.mTintMode;
         }
 
         @Override
@@ -276,6 +328,6 @@
             mColorState = state;
         }
 
-        // No local properties to initialize.
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
     }
 }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 18e8e52..40b55a7 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -17,13 +17,6 @@
 package android.graphics.drawable;
 
 import android.annotation.NonNull;
-import android.graphics.Insets;
-import android.graphics.Xfermode;
-import android.os.Trace;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -31,15 +24,19 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Insets;
 import android.graphics.NinePatch;
 import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.PorterDuff.Mode;
+import android.graphics.Xfermode;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.StateSet;
@@ -47,6 +44,9 @@
 import android.util.Xml;
 import android.view.View;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
@@ -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")) {
@@ -1224,6 +1226,26 @@
     }
 
     /**
+     * Ensures the tint filter is consistent with the current tint color and
+     * mode.
+     */
+    PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint,
+            PorterDuff.Mode tintMode) {
+        if (tint == null || tintMode == null) {
+            return null;
+        }
+
+        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
+        if (tintFilter == null) {
+            return new PorterDuffColorFilter(color, tintMode);
+        }
+
+        tintFilter.setColor(color);
+        tintFilter.setMode(tintMode);
+        return tintFilter;
+    }
+
+    /**
      * Obtains styled attributes from the theme, if available, or unstyled
      * resources if the theme is null.
      */
@@ -1238,8 +1260,10 @@
     /**
      * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
      * attribute's enum value.
+     *
+     * @hide
      */
-    static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
+    public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
         switch (value) {
             case 3: return Mode.SRC_OVER;
             case 5: return Mode.SRC_IN;
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 2aef39f..ed44cde 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -16,13 +16,17 @@
 
 package android.graphics.drawable;
 
+import android.annotation.NonNull;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Insets;
+import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.PorterDuff.Mode;
 import android.os.SystemClock;
 import android.util.LayoutDirection;
 import android.util.SparseArray;
@@ -63,8 +67,6 @@
     private long mExitAnimationEnd;
     private Drawable mLastDrawable;
 
-    private Insets mInsets = Insets.NONE;
-
     // overrides from Drawable
 
     @Override
@@ -116,7 +118,18 @@
      */
     @Override
     public Insets getOpticalInsets() {
-        return mInsets;
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.getOpticalInsets();
+        }
+        return Insets.NONE;
+    }
+
+    @Override
+    public boolean getOutline(@NonNull Outline outline) {
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.getOutline(outline);
+        }
+        return false;
     }
 
     @Override
@@ -151,7 +164,7 @@
 
     @Override
     public void setColorFilter(ColorFilter cf) {
-        mDrawableContainerState.mHasColorFilter = true;
+        mDrawableContainerState.mHasColorFilter = (cf != null);
 
         if (mDrawableContainerState.mColorFilter != cf) {
             mDrawableContainerState.mColorFilter = cf;
@@ -162,6 +175,20 @@
         }
     }
 
+    @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mDrawableContainerState.mHasTint = (tint != null && tintMode != null);
+
+        if (mDrawableContainerState.mTint != tint || mDrawableContainerState.mTintMode != tintMode) {
+            mDrawableContainerState.mTint = tint;
+            mDrawableContainerState.mTintMode = tintMode;
+
+            if (mCurrDrawable != null) {
+                mCurrDrawable.mutate().setTint(tint, tintMode);
+            }
+        }
+    }
+
     /**
      * Change the global fade duration when a new drawable is entering
      * the scene.
@@ -187,9 +214,6 @@
         }
         if (mCurrDrawable != null) {
             mCurrDrawable.setBounds(bounds);
-
-            // Must obtain optical insets after setting bounds.
-            mInsets = mCurrDrawable.getOpticalInsets();
         }
     }
 
@@ -396,6 +420,8 @@
                 }
                 if (mDrawableContainerState.mHasColorFilter) {
                     d.setColorFilter(mDrawableContainerState.mColorFilter);
+                } else if (mDrawableContainerState.mHasTint) {
+                    d.setTint(mDrawableContainerState.mTint, mDrawableContainerState.mTintMode);
                 }
                 d.setVisible(isVisible(), true);
                 d.setDither(mDrawableContainerState.mDither);
@@ -404,15 +430,9 @@
                 d.setBounds(getBounds());
                 d.setLayoutDirection(getLayoutDirection());
                 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
-
-                // Must obtain optical insets after setting bounds.
-                mInsets = d.getOpticalInsets();
-            } else {
-                mInsets = Insets.NONE;
             }
         } else {
             mCurrDrawable = null;
-            mInsets = Insets.NONE;
             mCurIndex = -1;
         }
 
@@ -566,6 +586,10 @@
         ColorFilter mColorFilter;
         boolean mHasColorFilter;
 
+        ColorStateList mTint;
+        Mode mTintMode;
+        boolean mHasTint;
+
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
@@ -588,6 +612,9 @@
                 mAutoMirrored = orig.mAutoMirrored;
                 mColorFilter = orig.mColorFilter;
                 mHasColorFilter = orig.mHasColorFilter;
+                mTint = orig.mTint;
+                mTintMode = orig.mTintMode;
+                mHasTint = orig.mHasTint;
 
                 // Cloning the following values may require creating futures.
                 mConstantPadding = orig.getConstantPadding();
@@ -741,7 +768,7 @@
             final int N = mNumChildren;
             final Drawable[] drawables = mDrawables;
             for (int i = 0; i < N; i++) {
-                final Drawable d = drawables[i]; 
+                final Drawable d = drawables[i];
                 if (d != null) {
                     if (d.canApplyTheme()) {
                         return true;
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 005b8ef..0ccce93 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -135,18 +135,17 @@
     private Paint mStrokePaint;   // optional, set by the caller
     private ColorFilter mColorFilter;   // optional, set by the caller
     private int mAlpha = 0xFF;  // modified by the caller
-    private boolean mDither;
 
     private final Path mPath = new Path();
     private final RectF mRect = new RectF();
 
     private Paint mLayerPaint;    // internal, used if we use saveLayer()
-    private boolean mRectIsDirty;   // internal state
+    private boolean mGradientIsDirty;   // internal state
     private boolean mMutated;
     private Path mRingPath;
     private boolean mPathIsDirty = true;
 
-    /** Current gradient radius, valid when {@link #mRectIsDirty} is false. */
+    /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */
     private float mGradientRadius;
 
     /**
@@ -384,7 +383,7 @@
      */
     public void setGradientType(int gradient) {
         mGradientState.setGradientType(gradient);
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -403,7 +402,7 @@
      */
     public void setGradientCenter(float x, float y) {
         mGradientState.setGradientCenter(x, y);
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -421,7 +420,7 @@
      */
     public void setGradientRadius(float gradientRadius) {
         mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX);
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -455,7 +454,7 @@
      */
     public void setUseLevel(boolean useLevel) {
         mGradientState.mUseLevel = useLevel;
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -483,7 +482,7 @@
      */
     public void setOrientation(Orientation orientation) {
         mGradientState.mOrientation = orientation;
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -501,7 +500,7 @@
      */
     public void setColors(int[] colors) {
         mGradientState.setColors(colors);
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         invalidateSelf();
     }
 
@@ -543,7 +542,7 @@
             if (mLayerPaint == null) {
                 mLayerPaint = new Paint();
             }
-            mLayerPaint.setDither(mDither);
+            mLayerPaint.setDither(st.mDither);
             mLayerPaint.setAlpha(mAlpha);
             mLayerPaint.setColorFilter(mColorFilter);
 
@@ -561,14 +560,14 @@
                 individual paints
             */
             mFillPaint.setAlpha(currFillAlpha);
-            mFillPaint.setDither(mDither);
+            mFillPaint.setDither(st.mDither);
             mFillPaint.setColorFilter(mColorFilter);
-            if (mColorFilter != null && mGradientState.mColorStateList == null) {
+            if (mColorFilter != null && st.mColorStateList == null) {
                 mFillPaint.setColor(mAlpha << 24);
             }
             if (haveStroke) {
                 mStrokePaint.setAlpha(currStrokeAlpha);
-                mStrokePaint.setDither(mDither);
+                mStrokePaint.setDither(st.mDither);
                 mStrokePaint.setColorFilter(mColorFilter);
             }
         }
@@ -638,10 +637,11 @@
 
     private void buildPathIfDirty() {
         final GradientState st = mGradientState;
-        if (mPathIsDirty || mRectIsDirty) {
+        if (mPathIsDirty) {
+            ensureValidRect();
             mPath.reset();
             mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
-            mPathIsDirty = mRectIsDirty = false;
+            mPathIsDirty = false;
         }
     }
 
@@ -804,8 +804,8 @@
 
     @Override
     public void setDither(boolean dither) {
-        if (dither != mDither) {
-            mDither = dither;
+        if (dither != mGradientState.mDither) {
+            mGradientState.mDither = dither;
             invalidateSelf();
         }
     }
@@ -829,27 +829,27 @@
         super.onBoundsChange(r);
         mRingPath = null;
         mPathIsDirty = true;
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
     }
 
     @Override
     protected boolean onLevelChange(int level) {
         super.onLevelChange(level);
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         mPathIsDirty = true;
         invalidateSelf();
         return true;
     }
 
     /**
-     * This checks mRectIsDirty, and if it is true, recomputes both our drawing
+     * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
      * rectangle (mRect) and the gradient itself, since it depends on our
      * rectangle too.
      * @return true if the resulting rectangle is not empty, false otherwise
      */
     private boolean ensureValidRect() {
-        if (mRectIsDirty) {
-            mRectIsDirty = false;
+        if (mGradientIsDirty) {
+            mGradientIsDirty = false;
 
             Rect bounds = getBounds();
             float inset = 0;
@@ -1015,7 +1015,7 @@
         state.mThemeAttrs = a.extractThemeAttrs();
 
         state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
-        mDither = a.getBoolean(R.styleable.GradientDrawable_dither, mDither);
+        state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
 
         if (state.mShape == RING) {
             state.mInnerRadius = a.getDimensionPixelSize(
@@ -1459,6 +1459,8 @@
         public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
         public int mInnerRadius = -1;
         public int mThickness = -1;
+        public boolean mDither = false;
+
         private float mCenterX = 0.5f;
         private float mCenterY = 0.5f;
         private float mGradientRadius = 0.5f;
@@ -1510,6 +1512,7 @@
             mThicknessRatio = state.mThicknessRatio;
             mInnerRadius = state.mInnerRadius;
             mThickness = state.mThickness;
+            mDither = state.mDither;
             mCenterX = state.mCenterX;
             mCenterY = state.mCenterY;
             mGradientRadius = state.mGradientRadius;
@@ -1672,7 +1675,7 @@
 
         initializeWithState(state);
 
-        mRectIsDirty = true;
+        mGradientIsDirty = true;
         mMutated = false;
     }
 
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 9e0ab86..220e81c 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -19,10 +19,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
 import android.graphics.*;
+import android.graphics.PorterDuff.Mode;
 import android.util.AttributeSet;
 import android.util.Log;
 
@@ -44,8 +46,7 @@
  * @attr ref android.R.styleable#InsetDrawable_insetTop
  * @attr ref android.R.styleable#InsetDrawable_insetBottom
  */
-public class InsetDrawable extends Drawable implements Drawable.Callback
-{
+public class InsetDrawable extends Drawable implements Drawable.Callback {
     // Most of this is copied from ScaleDrawable.
     private InsetState mInsetState;
     private final Rect mTmpRect = new Rect();
@@ -62,13 +63,13 @@
     public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,
                          int insetRight, int insetBottom) {
         this(null, null);
-        
+
         mInsetState.mDrawable = drawable;
         mInsetState.mInsetLeft = insetLeft;
         mInsetState.mInsetTop = insetTop;
         mInsetState.mInsetRight = insetRight;
         mInsetState.mInsetBottom = insetBottom;
-        
+
         if (drawable != null) {
             drawable.setCallback(this);
         }
@@ -78,7 +79,7 @@
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
             throws XmlPullParserException, IOException {
         int type;
-        
+
         TypedArray a = r.obtainAttributes(attrs,
                 com.android.internal.R.styleable.InsetDrawable);
 
@@ -168,7 +169,7 @@
                 | mInsetState.mChangingConfigurations
                 | mInsetState.mDrawable.getChangingConfigurations();
     }
-    
+
     @Override
     public boolean getPadding(Rect padding) {
         boolean pad = mInsetState.mDrawable.getPadding(padding);
@@ -178,7 +179,7 @@
         padding.top += mInsetState.mInsetTop;
         padding.bottom += mInsetState.mInsetBottom;
 
-        if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | 
+        if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight |
                     mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) {
             return true;
         } else {
@@ -217,6 +218,11 @@
         mInsetState.mDrawable.setColorFilter(cf);
     }
 
+    @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mInsetState.mDrawable.setTint(tint, tintMode);
+    }
+
     /** {@hide} */
     @Override
     public void setLayoutDirection(int layoutDirection) {
@@ -227,7 +233,7 @@
     public int getOpacity() {
         return mInsetState.mDrawable.getOpacity();
     }
-    
+
     @Override
     public boolean isStateful() {
         return mInsetState.mDrawable.isStateful();
@@ -239,7 +245,12 @@
         onBoundsChange(getBounds());
         return changed;
     }
-    
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        return mInsetState.mDrawable.setLevel(level);
+    }
+
     @Override
     protected void onBoundsChange(Rect bounds) {
         final Rect r = mTmpRect;
@@ -321,12 +332,12 @@
         public Drawable newDrawable() {
             return new InsetDrawable(this, null);
         }
-        
+
         @Override
         public Drawable newDrawable(Resources res) {
             return new InsetDrawable(this, res);
         }
-        
+
         @Override
         public int getChangingConfigurations() {
             return mChangingConfigurations;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 27f0a9d..2f22392 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -16,12 +16,16 @@
 
 package android.graphics.drawable;
 
+import android.annotation.NonNull;
+import android.content.res.ColorStateList;
 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.Outline;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
@@ -561,6 +565,16 @@
         }
     }
 
+    /**
+     * Builds an Outline from the first child Drawable, if present.
+     */
+    @Override
+    public boolean getOutline(@NonNull Outline outline) {
+        if (mLayerState.mNum < 1) return false;
+        final Drawable firstChild = mLayerState.mChildren[0].mDrawable;
+        return firstChild.getOutline(outline);
+    }
+
     @Override
     public void setHotspot(float x, float y) {
         final ChildDrawable[] array = mLayerState.mChildren;
@@ -630,6 +644,15 @@
         }
     }
 
+    @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNum;
+        for (int i = 0; i < N; i++) {
+            array[i].mDrawable.setTint(tint, tintMode);
+        }
+    }
+
     /**
      * Sets the opacity of this drawable directly, instead of collecting the
      * states from the layers
diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
index 9e56f67..c484094 100644
--- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
+++ b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
@@ -27,8 +27,10 @@
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Paint.Cap;
 import android.graphics.Paint.Style;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -67,6 +69,7 @@
     private final Ring mRing;
 
     private MaterialProgressState mState;
+    private PorterDuffColorFilter mTintFilter;
 
     /** Canvas rotation in degrees. */
     private float mRotation;
@@ -106,6 +109,8 @@
             float insets = minEdge / 2.0f - state.mInnerRadius;
             ring.setInsets(insets);
         }
+
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
     }
 
     @Override
@@ -118,15 +123,21 @@
     }
 
     @Override
-    protected boolean onStateChange(int[] state) {
-        boolean changed = super.onStateChange(state);
+    protected boolean onStateChange(int[] stateSet) {
+        boolean changed = super.onStateChange(stateSet);
 
-        final int color = mState.mColor.getColorForState(state, Color.TRANSPARENT);
+        final MaterialProgressState state = mState;
+        final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT);
         if (color != mRing.getColor()) {
             mRing.setColor(color);
             changed = true;
         }
 
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            changed = true;
+        }
+
         return changed;
     }
 
@@ -223,11 +234,24 @@
         return mRing.getColorFilter();
     }
 
+    @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        if (mState.mTint != tint || mState.mTintMode != tintMode) {
+            mState.mTint = tint;
+            mState.mTintMode = tintMode;
+
+            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
+            invalidateSelf();
+        }
+    }
+
+    @SuppressWarnings("unused")
     private void setRotation(float rotation) {
         mRotation = rotation;
         invalidateSelf();
     }
 
+    @SuppressWarnings("unused")
     private float getRotation() {
         return mRotation;
     }
@@ -331,6 +355,8 @@
         private int mWidth = -1;
         private int mHeight = -1;
         private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT);
+        private ColorStateList mTint = null;
+        private Mode mTintMode = null;
 
         public MaterialProgressState(MaterialProgressState orig) {
             if (orig != null) {
@@ -340,6 +366,8 @@
                 mWidth = orig.mWidth;
                 mHeight = orig.mHeight;
                 mColor = orig.mColor;
+                mTint = orig.mTint;
+                mTintMode = orig.mTintMode;
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index fea68ee..28335ea 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -327,25 +327,12 @@
 
     @Override
     public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) {
-        mNinePatchState.mTint = tint;
-        mNinePatchState.mTintMode = tintMode;
-        computeTintFilter();
-        invalidateSelf();
-    }
-
-    private void computeTintFilter() {
         final NinePatchState state = mNinePatchState;
-        if (state.mTint != null && state.mTintMode != null) {
-            final int color = state.mTint.getColorForState(getState(), 0);
-            if (mTintFilter != null) {
-                mTintFilter.setColor(color);
-                mTintFilter.setMode(state.mTintMode);
-            } else {
-                mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
-            }
-        } else {
-            mTintFilter = null;
-        }
+        state.mTint = tint;
+        state.mTintMode = tintMode;
+
+        mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
+        invalidateSelf();
     }
 
     @Override
@@ -549,15 +536,10 @@
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
-        final ColorStateList tint = mNinePatchState.mTint;
-        if (tint != null) {
-            final int newColor = tint.getColorForState(stateSet, 0);
-            final int oldColor = mTintFilter.getColor();
-            if (oldColor != newColor) {
-                mTintFilter.setColor(newColor);
-                invalidateSelf();
-                return true;
-            }
+        final NinePatchState state = mNinePatchState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
         }
 
         return false;
@@ -689,7 +671,7 @@
             mPadding = new Rect(state.mPadding);
         }
 
-        computeTintFilter();
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
         setNinePatch(state.mNinePatch);
     }
 }
diff --git a/graphics/java/android/graphics/drawable/PaintDrawable.java b/graphics/java/android/graphics/drawable/PaintDrawable.java
index c71cda1..a82e7b9 100644
--- a/graphics/java/android/graphics/drawable/PaintDrawable.java
+++ b/graphics/java/android/graphics/drawable/PaintDrawable.java
@@ -35,7 +35,7 @@
     public PaintDrawable(int color) {
         getPaint().setColor(color);
     }
-    
+
     /**
      * Specify radius for the corners of the rectangle. If this is > 0, then the
      * drawable is drawn in a round-rectangle, rather than a rectangle.
@@ -51,7 +51,7 @@
         }
         setCornerRadii(radii);
     }
-    
+
     /**
      * Specify radii for each of the 4 corners. For each corner, the array
      * contains 2 values, [X_radius, Y_radius]. The corners are ordered
@@ -78,9 +78,9 @@
             int radius = a.getDimensionPixelSize(
                                 com.android.internal.R.styleable.DrawableCorners_radius, 0);
             setCornerRadius(radius);
-            
+
             // now check of they have any per-corner radii
-            
+
             int topLeftRadius = a.getDimensionPixelSize(
                     com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius);
             int topRightRadius = a.getDimensionPixelSize(
diff --git a/graphics/java/android/graphics/drawable/PictureDrawable.java b/graphics/java/android/graphics/drawable/PictureDrawable.java
index cb2d8f6..6dcda1f 100644
--- a/graphics/java/android/graphics/drawable/PictureDrawable.java
+++ b/graphics/java/android/graphics/drawable/PictureDrawable.java
@@ -25,7 +25,7 @@
 
 /**
  * Drawable subclass that wraps a Picture, allowing the picture to be used
- * whereever a Drawable is supported.
+ * wherever a Drawable is supported.
  */
 public class PictureDrawable extends Drawable {
 
@@ -40,7 +40,7 @@
     public PictureDrawable(Picture picture) {
         mPicture = picture;
     }
-    
+
     /**
      * Return the picture associated with the drawable. May be null.
      *
@@ -49,7 +49,7 @@
     public Picture getPicture() {
         return mPicture;
     }
-    
+
     /**
      * Associate a picture with this drawable. The picture may be null.
      *
@@ -58,7 +58,7 @@
     public void setPicture(Picture picture) {
         mPicture = picture;
     }
-    
+
     @Override
     public void draw(Canvas canvas) {
         if (mPicture != null) {
@@ -86,16 +86,16 @@
         // not sure, so be safe
         return PixelFormat.TRANSLUCENT;
     }
-    
+
     @Override
     public void setFilterBitmap(boolean filter) {}
-    
+
     @Override
     public void setDither(boolean dither) {}
-    
+
     @Override
     public void setColorFilter(ColorFilter colorFilter) {}
-    
+
     @Override
     public void setAlpha(int alpha) {}
 }
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 345400e..2d49365 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -142,14 +142,16 @@
     }
 
     private void clampStartingPosition() {
-        final float dX = mStartingX - mBounds.exactCenterX();
-        final float dY = mStartingY - mBounds.exactCenterY();
+        final float cX = mBounds.exactCenterX();
+        final float cY = mBounds.exactCenterY();
+        final float dX = mStartingX - cX;
+        final float dY = mStartingY - cY;
         final float r = mOuterRadius;
         if (dX * dX + dY * dY > r * r) {
             // Point is outside the circle, clamp to the circumference.
             final double angle = Math.atan2(dY, dX);
-            mClampedStartingX = (float) (Math.cos(angle) * r);
-            mClampedStartingY = (float) (Math.sin(angle) * r);
+            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
+            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
         } else {
             mClampedStartingX = mStartingX;
             mClampedStartingY = mStartingY;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0512ecc..9bf7c43 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,8 @@
 
 package android.graphics.drawable;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -52,7 +54,7 @@
  * <code>&lt!-- A red ripple masked against an opaque rectangle. --/>
  * &ltripple android:color="#ffff0000">
  *   &ltitem android:id="@android:id/mask"
- *         android:drawable="#ffffffff" />
+ *         android:drawable="@android:color/white" />
  * &ltripple /></code>
  * </pre>
  * <p>
@@ -62,9 +64,9 @@
  * If no mask layer is set, the ripple effect is masked against the composite
  * of the child layers.
  * <pre>
- * <code>&lt!-- A blue ripple drawn atop a green rectangle. --/>
+ * <code>&lt!-- A blue ripple drawn atop a black rectangle. --/>
  * &ltripple android:color="#ff00ff00">
- *   &ltitem android:drawable="#ff0000ff" />
+ *   &ltitem android:drawable="@android:color/black" />
  * &ltripple />
  *
  * &lt!-- A red ripple drawn atop a drawable resource. --/>
@@ -147,11 +149,7 @@
     }
 
     /**
-     * Creates a new ripple drawable with the specified content and mask
-     * drawables.
-     *
-     * @param content The content drawable, may be {@code null}
-     * @param mask The mask drawable, may be {@code null}
+     * @hide TO BE REMOVED FOR L-RELEASE
      */
     public RippleDrawable(Drawable content, Drawable mask) {
         this(new RippleState(null, null, null), null, null);
@@ -165,6 +163,36 @@
         }
 
         ensurePadding();
+
+        Log.e(LOG_TAG, "This constructor is being removed", new RuntimeException());
+    }
+
+    /**
+     * Creates a new ripple drawable with the specified ripple color and
+     * optional content and mask drawables.
+     *
+     * @param color The ripple color
+     * @param content The content drawable, may be {@code null}
+     * @param mask The mask drawable, may be {@code null}
+     */
+    public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
+            @Nullable Drawable mask) {
+        this(new RippleState(null, null, null), null, null);
+
+        if (color == null) {
+            throw new IllegalArgumentException("RippleDrawable requires a non-null color");
+        }
+
+        if (content != null) {
+            addLayer(content, null, 0, 0, 0, 0, 0);
+        }
+
+        if (mask != null) {
+            addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+        }
+
+        setColor(color);
+        ensurePadding();
     }
 
     @Override
@@ -645,25 +673,29 @@
 
     @Override
     public Rect getDirtyBounds() {
-        final Rect drawingBounds = mDrawingBounds;
-        final Rect dirtyBounds = mDirtyBounds;
-        dirtyBounds.set(drawingBounds);
-        drawingBounds.setEmpty();
+        if (isProjected()) {
+            final Rect drawingBounds = mDrawingBounds;
+            final Rect dirtyBounds = mDirtyBounds;
+            dirtyBounds.set(drawingBounds);
+            drawingBounds.setEmpty();
 
-        final int cX = (int) mHotspotBounds.exactCenterX();
-        final int cY = (int) mHotspotBounds.exactCenterY();
-        final Rect rippleBounds = mTempRect;
-        final Ripple[] activeRipples = mAnimatingRipples;
-        final int N = mAnimatingRipplesCount;
-        for (int i = 0; i < N; i++) {
-            activeRipples[i].getBounds(rippleBounds);
-            rippleBounds.offset(cX, cY);
-            drawingBounds.union(rippleBounds);
+            final int cX = (int) mHotspotBounds.exactCenterX();
+            final int cY = (int) mHotspotBounds.exactCenterY();
+            final Rect rippleBounds = mTempRect;
+            final Ripple[] activeRipples = mAnimatingRipples;
+            final int N = mAnimatingRipplesCount;
+            for (int i = 0; i < N; i++) {
+                activeRipples[i].getBounds(rippleBounds);
+                rippleBounds.offset(cX, cY);
+                drawingBounds.union(rippleBounds);
+            }
+
+            dirtyBounds.union(drawingBounds);
+            dirtyBounds.union(super.getDirtyBounds());
+            return dirtyBounds;
+        } else {
+            return getBounds();
         }
-
-        dirtyBounds.union(drawingBounds);
-        dirtyBounds.union(super.getDirtyBounds());
-        return dirtyBounds;
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 06aeb98..8f8fa98 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -22,6 +22,8 @@
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Rect;
+import android.graphics.PorterDuff.Mode;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
@@ -137,6 +139,11 @@
     }
 
     @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mState.mDrawable.setTint(tint, tintMode);
+    }
+
+    @Override
     public int getOpacity() {
         return mState.mDrawable.getOpacity();
     }
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index f090c11..46c92fe 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -19,10 +19,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
 import android.graphics.*;
+import android.graphics.PorterDuff.Mode;
 import android.view.Gravity;
 import android.util.AttributeSet;
 
@@ -188,6 +190,11 @@
     }
 
     @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        mScaleState.mDrawable.setTint(tint, tintMode);
+    }
+
+    @Override
     public int getOpacity() {
         return mScaleState.mDrawable.getOpacity();
     }
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 024f77c..86765dd 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -24,6 +24,7 @@
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
@@ -32,6 +33,7 @@
 import android.content.res.Resources.Theme;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -74,7 +76,7 @@
      * ShapeDrawable constructor.
      */
     public ShapeDrawable() {
-        this((ShapeState) null);
+        this(new ShapeState(null), null, null);
     }
 
     /**
@@ -83,20 +85,11 @@
      * @param s the Shape that this ShapeDrawable should be
      */
     public ShapeDrawable(Shape s) {
-        this((ShapeState) null);
+        this(new ShapeState(null), null, null);
 
         mShapeState.mShape = s;
     }
 
-    private ShapeDrawable(ShapeState state) {
-        mShapeState = new ShapeState(state);
-
-        if (state != null && state.mTint != null) {
-            final int color = state.mTint.getColorForState(getState(), 0);
-            mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
-        }
-    }
-
     /**
      * Returns the Shape of this ShapeDrawable.
      */
@@ -292,31 +285,13 @@
     }
 
     @Override
-    public void setTint(ColorStateList tint, Mode tintMode) {
-        if (mShapeState.mTint != tint || mShapeState.mTintMode != tintMode) {
-            mShapeState.mTint = tint;
-            mShapeState.mTintMode = tintMode;
-            updateTintFilter();
-            invalidateSelf();
-        }
-    }
+    public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) {
+        final ShapeState state = mShapeState;
+        state.mTint = tint;
+        state.mTintMode = tintMode;
 
-    /**
-     * Ensures the tint filter is consistent with the current tint color and
-     * mode.
-     */
-    private void updateTintFilter() {
-        final ColorStateList tint = mShapeState.mTint;
-        final Mode tintMode = mShapeState.mTintMode;
-        if (tint != null && tintMode != null) {
-            if (mTintFilter == null) {
-                mTintFilter = new PorterDuffColorFilter(0, tintMode);
-            } else {
-                mTintFilter.setMode(tintMode);
-            }
-        } else {
-            mTintFilter = null;
-        }
+        mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
+        invalidateSelf();
     }
 
     @Override
@@ -357,17 +332,11 @@
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
-        final ColorStateList tint = mShapeState.mTint;
-        if (tint != null) {
-            final int newColor = tint.getColorForState(stateSet, 0);
-            final int oldColor = mTintFilter.getColor();
-            if (oldColor != newColor) {
-                mTintFilter.setColor(newColor);
-                invalidateSelf();
-                return true;
-            }
+        final ShapeState state = mShapeState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
         }
-
         return false;
     }
 
@@ -408,20 +377,8 @@
             throws XmlPullParserException, IOException {
         super.inflate(r, parser, attrs, theme);
 
-        TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ShapeDrawable);
-
-        int color = mShapeState.mPaint.getColor();
-        color = a.getColor(com.android.internal.R.styleable.ShapeDrawable_color, color);
-        mShapeState.mPaint.setColor(color);
-
-        boolean dither = a.getBoolean(com.android.internal.R.styleable.ShapeDrawable_dither, false);
-        mShapeState.mPaint.setDither(dither);
-
-        setIntrinsicWidth((int)
-                a.getDimension(com.android.internal.R.styleable.ShapeDrawable_width, 0f));
-        setIntrinsicHeight((int)
-                a.getDimension(com.android.internal.R.styleable.ShapeDrawable_height, 0f));
-
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable);
+        updateStateFromTypedArray(a);
         a.recycle();
 
         int type;
@@ -439,6 +396,57 @@
                         " for ShapeDrawable " + this);
             }
         }
+
+        // Update local properties.
+        initializeWithState(mShapeState, r);
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final ShapeState state = mShapeState;
+        if (state == null || state.mThemeAttrs == null) {
+            return;
+        }
+
+        final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        // Update local properties.
+        initializeWithState(state, t.getResources());
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final ShapeState state = mShapeState;
+        final Paint paint = state.mPaint;
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        int color = paint.getColor();
+        color = a.getColor(R.styleable.ShapeDrawable_color, color);
+        paint.setColor(color);
+
+        boolean dither = paint.isDither();
+        dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
+        paint.setDither(dither);
+
+        setIntrinsicWidth((int) a.getDimension(
+                R.styleable.ShapeDrawable_width, state.mIntrinsicWidth));
+        setIntrinsicHeight((int) a.getDimension(
+                R.styleable.ShapeDrawable_height, state.mIntrinsicHeight));
+
+        final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
     }
 
     private void updateShape() {
@@ -498,6 +506,7 @@
      * Defines the intrinsic properties of this ShapeDrawable's Shape.
      */
     final static class ShapeState extends ConstantState {
+        int[] mThemeAttrs;
         int mChangingConfigurations;
         Paint mPaint;
         Shape mShape;
@@ -511,6 +520,7 @@
 
         ShapeState(ShapeState orig) {
             if (orig != null) {
+                mThemeAttrs = orig.mThemeAttrs;
                 mPaint = orig.mPaint;
                 mShape = orig.mShape;
                 mTint = orig.mTint;
@@ -526,13 +536,23 @@
         }
 
         @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null;
+        }
+
+        @Override
         public Drawable newDrawable() {
-            return new ShapeDrawable(this);
+            return new ShapeDrawable(this, null, null);
         }
 
         @Override
         public Drawable newDrawable(Resources res) {
-            return new ShapeDrawable(this);
+            return new ShapeDrawable(this, res, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res, Theme theme) {
+            return new ShapeDrawable(this, res, theme);
         }
 
         @Override
@@ -542,6 +562,30 @@
     }
 
     /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    private ShapeDrawable(ShapeState state, Resources res, Theme theme) {
+        if (theme != null && state.canApplyTheme()) {
+            mShapeState = new ShapeState(state);
+            applyTheme(theme);
+        } else {
+            mShapeState = state;
+        }
+
+        initializeWithState(state, res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void initializeWithState(ShapeState state, Resources res) {
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+    }
+
+    /**
      * Base class defines a factory object that is called each time the drawable
      * is resized (has a new width or height). Its resize() method returns a
      * corresponding shader, or null. Implement this class if you'd like your
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index b2fac9b..f359fdd 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -235,10 +235,10 @@
     public Drawable getStateDrawable(int index) {
         return mStateListState.getChild(index);
     }
-    
+
     /**
      * Gets the index of the drawable with the provided state set.
-     * 
+     *
      * @param stateSet the state set to look up
      * @return the index of the provided state set, or -1 if not found
      * @hide pending API council
@@ -248,7 +248,7 @@
     public int getStateDrawableIndex(int[] stateSet) {
         return mStateListState.indexOfStateSet(stateSet);
     }
-    
+
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 622e90b..4380ca4e 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -42,20 +42,20 @@
 public class TransitionDrawable extends LayerDrawable implements Drawable.Callback {
 
     /**
-     * A transition is about to start. 
+     * A transition is about to start.
      */
     private static final int TRANSITION_STARTING = 0;
-    
+
     /**
      * The transition has started and the animation is in progress
      */
     private static final int TRANSITION_RUNNING = 1;
-   
+
     /**
      * No transition will be applied
      */
     private static final int TRANSITION_NONE = 2;
-   
+
     /**
      * The current state of the transition. One of {@link #TRANSITION_STARTING},
      * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE}
@@ -101,10 +101,10 @@
     LayerState createConstantState(LayerState state, Resources res) {
         return new TransitionState((TransitionState) state, this, res);
     }
-    
+
     /**
      * Begin the second layer on top of the first layer.
-     * 
+     *
      * @param durationMillis The length of the transition in milliseconds
      */
     public void startTransition(int durationMillis) {
@@ -116,7 +116,7 @@
         mTransitionState = TRANSITION_STARTING;
         invalidateSelf();
     }
-    
+
     /**
      * Show only the first layer.
      */
@@ -184,7 +184,7 @@
                 }
                 break;
         }
-      
+
         final int alpha = mAlpha;
         final boolean crossFade = mCrossFade;
         final ChildDrawable[] array = mLayerState.mChildren;
@@ -217,7 +217,7 @@
             d.draw(canvas);
             d.setAlpha(0xFF);
         }
-        
+
         if (!done) {
             invalidateSelf();
         }
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 9a63fa3..c98f2a1 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -14,20 +14,26 @@
 
 package android.graphics.drawable;
 
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 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;
 import android.graphics.Path;
 import android.graphics.PathMeasure;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.PorterDuff.Mode;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.PathParser;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -39,8 +45,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 +63,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 +98,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 +117,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,20 +135,33 @@
     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>();
+
+    private PorterDuffColorFilter mTintFilter;
 
     public VectorDrawable() {
         mVectorState = new VectorDrawableState(null);
     }
 
     private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
-        mVectorState = new VectorDrawableState(state);
-
-        if (theme != null && canApplyTheme()) {
+        if (theme != null && state.canApplyTheme()) {
+            // If we need to apply a theme, implicitly mutate.
+            mVectorState = new VectorDrawableState(state);
             applyTheme(theme);
+        } else {
+            mVectorState = state;
         }
+
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
+    }
+
+    Object getTargetByName(String name) {
+        return mVGTargetsMap.get(name);
     }
 
     @Override
@@ -150,53 +179,64 @@
     }
 
     @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();
         }
     }
 
     @Override
     public void setColorFilter(ColorFilter colorFilter) {
-        mVectorState.mVPathRenderer.setColorFilter(colorFilter);
+        final VectorDrawableState state = mVectorState;
+        if (colorFilter != null) {
+            // Color filter overrides tint.
+            mTintFilter = null;
+        } else if (state.mTint != null && state.mTintMode != null) {
+            // Restore the tint filter, if we need one.
+            final int color = state.mTint.getColorForState(getState(), Color.TRANSPARENT);
+            mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
+            colorFilter = mTintFilter;
+        }
+
+        state.mVPathRenderer.setColorFilter(colorFilter);
         invalidateSelf();
     }
 
     @Override
+    public void setTint(ColorStateList tint, Mode tintMode) {
+        final VectorDrawableState state = mVectorState;
+        if (state.mTint != tint || state.mTintMode != tintMode) {
+            state.mTint = tint;
+            state.mTintMode = tintMode;
+
+            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
+            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final VectorDrawableState state = mVectorState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
 
-    /**
-     * Sets padding for this shape, defined by a Rect object. Define the padding
-     * in the Rect object as: left, top, right, bottom.
-     */
-    public void setPadding(Rect padding) {
-        setPadding(padding.left, padding.top, padding.right, padding.bottom);
-    }
-
-    /**
-     * Sets padding for the shape.
-     *
-     * @param left padding for the left side (in pixels)
-     * @param top padding for the top (in pixels)
-     * @param right padding for the right side (in pixels)
-     * @param bottom padding for the bottom (in pixels)
-     */
-    public void setPadding(int left, int top, int right, int bottom) {
-        if ((left | top | right | bottom) == 0) {
-            mVectorState.mPadding = null;
-        } else {
-            if (mVectorState.mPadding == null) {
-                mVectorState.mPadding = new Rect();
-            }
-            mVectorState.mPadding.set(left, top, right, bottom);
-        }
-        invalidateSelf();
-    }
-
     @Override
     public int getIntrinsicWidth() {
         return (int) mVectorState.mVPathRenderer.mBaseWidth;
@@ -208,23 +248,6 @@
     }
 
     @Override
-    public boolean getPadding(Rect padding) {
-        if (mVectorState.mPadding != null) {
-            padding.set(mVectorState.mPadding);
-            return true;
-        } else {
-            return super.getPadding(padding);
-        }
-    }
-
-    @Override
-    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
-            throws XmlPullParserException, IOException {
-        final VPathRenderer p = inflateInternal(res, parser, attrs, theme);
-        setPathRenderer(p);
-    }
-
-    @Override
     public boolean canApplyTheme() {
         return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
     }
@@ -260,24 +283,71 @@
         return null;
     }
 
-    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
-            Theme theme) throws XmlPullParserException, IOException {
+    private static int applyAlpha(int color, float alpha) {
+        int alphaBytes = Color.alpha(color);
+        color &= 0x00FFFFFF;
+        color |= ((int) (alphaBytes * alpha)) << 24;
+        return color;
+    }
+
+
+    @Override
+    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(res, theme,  attrs,R.styleable.VectorDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        final VectorDrawableState state = mVectorState;
+        state.mVPathRenderer = inflateInternal(res, parser, attrs, theme);
+
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        state.mVPathRenderer.setColorFilter(mTintFilter);
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final VectorDrawableState state = mVectorState;
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+    }
+
+    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 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 +355,29 @@
                 } 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);
+                    }
+                }
+            } 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,26 +402,39 @@
             throw new XmlPullParserException("no " + tag + " defined");
         }
 
-        pathRenderer.mCurrentGroup = currentGroup;
-        // post parse cleanup
-        pathRenderer.parseFinish();
         return pathRenderer;
     }
 
-    private void setPathRenderer(VPathRenderer pathRenderer) {
-        mVectorState.mVPathRenderer = 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 static class VectorDrawableState extends ConstantState {
+        int[] mThemeAttrs;
         int mChangingConfigurations;
         VPathRenderer mVPathRenderer;
-        Rect mPadding;
+        ColorStateList mTint;
+        Mode mTintMode;
 
         public VectorDrawableState(VectorDrawableState copy) {
             if (copy != null) {
+                mThemeAttrs = copy.mThemeAttrs;
                 mChangingConfigurations = copy.mChangingConfigurations;
+                // TODO: Make sure the constant state are handled correctly.
                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
-                mPadding = new Rect(copy.mPadding);
+                mTint = copy.mTint;
+                mTintMode = copy.mTintMode;
             }
         }
 
@@ -360,35 +460,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 +512,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 +577,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 +720,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;
-
         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;
@@ -612,29 +956,22 @@
         Paint.Join mStrokeLineJoin = Paint.Join.MITER;
         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 PathParser.PathDataNode[] mNode = null;
+        private String mPathName;
 
         public VPath() {
             // Empty constructor.
         }
 
-        public VPath(VPath p) {
-            copyFrom(p);
-        }
-
         public void toPath(Path path) {
             path.reset();
             if (mNode != null) {
-                VNode.createPath(mNode, path);
+                PathParser.PathDataNode.nodesToPath(mNode, path);
             }
         }
 
-        public String getID() {
-            return mId;
+        public String getPathName() {
+            return mPathName;
         }
 
         private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
@@ -663,6 +1000,102 @@
             }
         }
 
+        /* Setters and Getters, mostly used by animator from AnimatedVectorDrawable. */
+        @SuppressWarnings("unused")
+        public PathParser.PathDataNode[] getPathData() {
+            return mNode;
+        }
+
+        @SuppressWarnings("unused")
+        public void setPathData(PathParser.PathDataNode[] node) {
+            if (!PathParser.canMorph(mNode, node)) {
+                // This should not happen in the middle of animation.
+                mNode = PathParser.deepCopyNodes(node);
+            } else {
+                PathParser.updateNodes(mNode, node);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        int getStroke() {
+            return mStrokeColor;
+        }
+
+        @SuppressWarnings("unused")
+        void setStroke(int strokeColor) {
+            mStrokeColor = strokeColor;
+        }
+
+        @SuppressWarnings("unused")
+        float getStrokeWidth() {
+            return mStrokeWidth;
+        }
+
+        @SuppressWarnings("unused")
+        void setStrokeWidth(float strokeWidth) {
+            mStrokeWidth = strokeWidth;
+        }
+
+        @SuppressWarnings("unused")
+        float getStrokeOpacity() {
+            return mStrokeOpacity;
+        }
+
+        @SuppressWarnings("unused")
+        void setStrokeOpacity(float strokeOpacity) {
+            mStrokeOpacity = strokeOpacity;
+        }
+
+        @SuppressWarnings("unused")
+        int getFill() {
+            return mFillColor;
+        }
+
+        @SuppressWarnings("unused")
+        void setFill(int fillColor) {
+            mFillColor = fillColor;
+        }
+
+        @SuppressWarnings("unused")
+        float getFillOpacity() {
+            return mFillOpacity;
+        }
+
+        @SuppressWarnings("unused")
+        void setFillOpacity(float fillOpacity) {
+            mFillOpacity = fillOpacity;
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathStart() {
+            return mTrimPathStart;
+        }
+
+        @SuppressWarnings("unused")
+        void setTrimPathStart(float trimPathStart) {
+            mTrimPathStart = trimPathStart;
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathEnd() {
+            return mTrimPathEnd;
+        }
+
+        @SuppressWarnings("unused")
+        void setTrimPathEnd(float trimPathEnd) {
+            mTrimPathEnd = trimPathEnd;
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathOffset() {
+            return mTrimPathOffset;
+        }
+
+        @SuppressWarnings("unused")
+        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,11 +1108,12 @@
             }
 
             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) {
-                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+                mNode = PathParser.createNodesFromPathData(a.getString(
+                        R.styleable.VectorDrawablePath_pathData));
             }
 
             if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
@@ -690,18 +1124,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,20 +1192,17 @@
             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)) {
-                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+                mNode = PathParser.createNodesFromPathData(a.getString(
+                        R.styleable.VectorDrawablePath_pathData));
             }
 
             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,528 +1221,17 @@
                     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);
             }
         }
-
-        private static int nextStart(String s, int end) {
-            char c;
-
-            while (end < s.length()) {
-                c = s.charAt(end);
-                if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
-                    return end;
-                }
-                end++;
-            }
-            return end;
-        }
-
-        private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) {
-            list.add(new VectorDrawable.VNode(cmd, val));
-        }
-
-        /**
-         * parse the floats in the string
-         * this is an optimized version of
-         * parseFloat(s.split(",|\\s"));
-         *
-         * @param s the string containing a command and list of floats
-         * @return array of floats
-         */
-        private static float[] getFloats(String s) {
-            if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
-                return new float[0];
-            }
-            try {
-                float[] tmp = new float[s.length()];
-                int count = 0;
-                int pos = 1, end;
-                while ((end = extract(s, pos)) >= 0) {
-                    if (pos < end) {
-                        tmp[count++] = Float.parseFloat(s.substring(pos, end));
-                    }
-                    pos = end + 1;
-                }
-                // handle the final float if there is one
-                if (pos < s.length()) {
-                    tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
-                }
-                return Arrays.copyOf(tmp, count);
-            } catch (NumberFormatException e){
-                Log.e(LOGTAG,"error in parsing \""+s+"\"");
-                throw e;
-            }
-        }
-
-        /**
-         * calculate the position of the next comma or space
-         * @param s the string to search
-         * @param start the position to start searching
-         * @return the position of the next comma or space or -1 if none found
-         */
-        private static int extract(String s, int start) {
-            int space = s.indexOf(' ', start);
-            int comma = s.indexOf(',', start);
-            if (space == -1) {
-                return comma;
-            }
-            if (comma == -1) {
-                return space;
-            }
-            return (comma > space) ? space : comma;
-        }
-
-        private VectorDrawable.VNode[] parsePath(String value) {
-            int start = 0;
-            int end = 1;
-
-            ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>();
-            while (end < value.length()) {
-                end = nextStart(value, end);
-                String s = value.substring(start, end);
-                float[] val = getFloats(s);
-                addNode(list, s.charAt(0), val);
-
-                start = end;
-                end++;
-            }
-            if ((end - start) == 1 && start < value.length()) {
-
-                addNode(list, value.charAt(start), new float[0]);
-            }
-            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 {
-        private char mType;
-        private float[] mParams;
-
-        public VNode(char type, float[] params) {
-            mType = type;
-            mParams = params;
-        }
-
-        public VNode(VNode n) {
-            mType = n.mType;
-            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
-        }
-
-        public static void createPath(VNode[] node, Path path) {
-            float[] current = new float[4];
-            char previousCommand = 'm';
-            for (int i = 0; i < node.length; i++) {
-                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
-                previousCommand = node[i].mType;
-            }
-        }
-
-        private static void addCommand(Path path, float[] current,
-                char previousCmd, char cmd, float[] val) {
-
-            int incr = 2;
-            float currentX = current[0];
-            float currentY = current[1];
-            float ctrlPointX = current[2];
-            float ctrlPointY = current[3];
-            float reflectiveCtrlPointX;
-            float reflectiveCtrlPointY;
-
-            switch (cmd) {
-                case 'z':
-                case 'Z':
-                    path.close();
-                    return;
-                case 'm':
-                case 'M':
-                case 'l':
-                case 'L':
-                case 't':
-                case 'T':
-                    incr = 2;
-                    break;
-                case 'h':
-                case 'H':
-                case 'v':
-                case 'V':
-                    incr = 1;
-                    break;
-                case 'c':
-                case 'C':
-                    incr = 6;
-                    break;
-                case 's':
-                case 'S':
-                case 'q':
-                case 'Q':
-                    incr = 4;
-                    break;
-                case 'a':
-                case 'A':
-                    incr = 7;
-                    break;
-            }
-            for (int k = 0; k < val.length; k += incr) {
-                switch (cmd) {
-                    case 'm': // moveto - Start a new sub-path (relative)
-                        path.rMoveTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'M': // moveto - Start a new sub-path
-                        path.moveTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'l': // lineto - Draw a line from the current point (relative)
-                        path.rLineTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'L': // lineto - Draw a line from the current point
-                        path.lineTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'z': // closepath - Close the current subpath
-                    case 'Z': // closepath - Close the current subpath
-                        path.close();
-                        break;
-                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
-                        path.rLineTo(val[k + 0], 0);
-                        currentX += val[k + 0];
-                        break;
-                    case 'H': // horizontal lineto - Draws a horizontal line
-                        path.lineTo(val[k + 0], currentY);
-                        currentX = val[k + 0];
-                        break;
-                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
-                        path.rLineTo(0, val[k + 0]);
-                        currentY += val[k + 0];
-                        break;
-                    case 'V': // vertical lineto - Draws a vertical line from the current point
-                        path.lineTo(currentX, val[k + 0]);
-                        currentY = val[k + 0];
-                        break;
-                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
-                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-
-                        ctrlPointX = currentX + val[k + 2];
-                        ctrlPointY = currentY + val[k + 3];
-                        currentX += val[k + 4];
-                        currentY += val[k + 5];
-
-                        break;
-                    case 'C': // curveto - Draws a cubic Bézier curve
-                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-                        currentX = val[k + 4];
-                        currentY = val[k + 5];
-                        ctrlPointX = val[k + 2];
-                        ctrlPointY = val[k + 3];
-                        break;
-                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1],
-                                val[k + 2], val[k + 3]);
-
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 'q': // Draws a quadratic Bézier (relative)
-                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'Q': // Draws a quadratic Bézier
-                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = currentX + reflectiveCtrlPointX;
-                        ctrlPointY = currentY + reflectiveCtrlPointY;
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = reflectiveCtrlPointX;
-                        ctrlPointY = reflectiveCtrlPointY;
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'a': // Draws an elliptical arc
-                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5] + currentX,
-                                val[k + 6] + currentY,
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX += val[k + 5];
-                        currentY += val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                    case 'A': // Draws an elliptical arc
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5],
-                                val[k + 6],
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX = val[k + 5];
-                        currentY = val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                }
-                previousCmd = cmd;
-            }
-            current[0] = currentX;
-            current[1] = currentY;
-            current[2] = ctrlPointX;
-            current[3] = ctrlPointY;
-        }
-
-        private static void drawArc(Path p,
-                float x0,
-                float y0,
-                float x1,
-                float y1,
-                float a,
-                float b,
-                float theta,
-                boolean isMoreThanHalf,
-                boolean isPositiveArc) {
-
-            /* Convert rotation angle from degrees to radians */
-            double thetaD = Math.toRadians(theta);
-            /* Pre-compute rotation matrix entries */
-            double cosTheta = Math.cos(thetaD);
-            double sinTheta = Math.sin(thetaD);
-            /* Transform (x0, y0) and (x1, y1) into unit space */
-            /* using (inverse) rotation, followed by (inverse) scale */
-            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
-            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
-            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
-            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
-            /* Compute differences and averages */
-            double dx = x0p - x1p;
-            double dy = y0p - y1p;
-            double xm = (x0p + x1p) / 2;
-            double ym = (y0p + y1p) / 2;
-            /* Solve for intersecting unit circles */
-            double dsq = dx * dx + dy * dy;
-            if (dsq == 0.0) {
-                Log.w(LOGTAG, " Points are coincident");
-                return; /* Points are coincident */
-            }
-            double disc = 1.0 / dsq - 1.0 / 4.0;
-            if (disc < 0.0) {
-                Log.w(LOGTAG, "Points are too far apart " + dsq);
-                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
-                drawArc(p, x0, y0, x1, y1, a * adjust,
-                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
-                return; /* Points are too far apart */
-            }
-            double s = Math.sqrt(disc);
-            double sdx = s * dx;
-            double sdy = s * dy;
-            double cx;
-            double cy;
-            if (isMoreThanHalf == isPositiveArc) {
-                cx = xm - sdy;
-                cy = ym + sdx;
-            } else {
-                cx = xm + sdy;
-                cy = ym - sdx;
-            }
-
-            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
-            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
-            double sweep = (eta1 - eta0);
-            if (isPositiveArc != (sweep >= 0)) {
-                if (sweep > 0) {
-                    sweep -= 2 * Math.PI;
-                } else {
-                    sweep += 2 * Math.PI;
-                }
-            }
-
-            cx *= a;
-            cy *= b;
-            double tcx = cx;
-            cx = cx * cosTheta - cy * sinTheta;
-            cy = tcx * sinTheta + cy * cosTheta;
-
-            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-        }
-
-        /**
-         * Converts an arc to cubic Bezier segments and records them in p.
-         *
-         * @param p The target for the cubic Bezier segments
-         * @param cx The x coordinate center of the ellipse
-         * @param cy The y coordinate center of the ellipse
-         * @param a The radius of the ellipse in the horizontal direction
-         * @param b The radius of the ellipse in the vertical direction
-         * @param e1x E(eta1) x coordinate of the starting point of the arc
-         * @param e1y E(eta2) y coordinate of the starting point of the arc
-         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
-         * @param start The start angle of the arc on the ellipse
-         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
-         */
-        private static void arcToBezier(Path p,
-                double cx,
-                double cy,
-                double a,
-                double b,
-                double e1x,
-                double e1y,
-                double theta,
-                double start,
-                double sweep) {
-            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
-            // and http://www.spaceroots.org/documents/ellipse/node22.html
-
-            // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
-
-            double eta1 = start;
-            double cosTheta = Math.cos(theta);
-            double sinTheta = Math.sin(theta);
-            double cosEta1 = Math.cos(eta1);
-            double sinEta1 = Math.sin(eta1);
-            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
-            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
-            double anglePerSegment = sweep / numSegments;
-            for (int i = 0; i < numSegments; i++) {
-                double eta2 = eta1 + anglePerSegment;
-                double sinEta2 = Math.sin(eta2);
-                double cosEta2 = Math.cos(eta2);
-                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
-                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
-                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
-                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
-                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
-                double alpha =
-                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
-                double q1x = e1x + alpha * ep1x;
-                double q1y = e1y + alpha * ep1y;
-                double q2x = e2x - alpha * ep2x;
-                double q2y = e2y - alpha * ep2y;
-
-                p.cubicTo((float) q1x,
-                        (float) q1y,
-                        (float) q2x,
-                        (float) q2y,
-                        (float) e2x,
-                        (float) e2y);
-                eta1 = eta2;
-                e1x = e2x;
-                e1y = e2y;
-                ep1x = ep2x;
-                ep1y = ep2y;
-            }
-        }
-
     }
 }
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/include/androidfw/ByteBucketArray.h b/include/androidfw/ByteBucketArray.h
new file mode 100644
index 0000000..87c6b12
--- /dev/null
+++ b/include/androidfw/ByteBucketArray.h
@@ -0,0 +1,97 @@
+/*
+ * 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 __BYTE_BUCKET_ARRAY_H
+#define __BYTE_BUCKET_ARRAY_H
+
+#include <utils/Log.h>
+#include <stdint.h>
+#include <string.h>
+
+namespace android {
+
+/**
+ * Stores a sparsely populated array. Has a fixed size of 256
+ * (number of entries that a byte can represent).
+ */
+template<typename T>
+class ByteBucketArray {
+public:
+    ByteBucketArray() : mDefault() {
+        memset(mBuckets, 0, sizeof(mBuckets));
+    }
+
+    ~ByteBucketArray() {
+        for (size_t i = 0; i < NUM_BUCKETS; i++) {
+            if (mBuckets[i] != NULL) {
+                delete [] mBuckets[i];
+            }
+        }
+        memset(mBuckets, 0, sizeof(mBuckets));
+    }
+
+    inline size_t size() const {
+        return NUM_BUCKETS * BUCKET_SIZE;
+    }
+
+    inline const T& get(size_t index) const {
+        return (*this)[index];
+    }
+
+    const T& operator[](size_t index) const {
+        if (index >= size()) {
+            return mDefault;
+        }
+
+        uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
+        T* bucket = mBuckets[bucketIndex];
+        if (bucket == NULL) {
+            return mDefault;
+        }
+        return bucket[0x0f & static_cast<uint8_t>(index)];
+    }
+
+    T& editItemAt(size_t index) {
+        ALOG_ASSERT(index < size(), "ByteBucketArray.getOrCreate(index=%u) with size=%u",
+                (uint32_t) index, (uint32_t) size());
+
+        uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
+        T* bucket = mBuckets[bucketIndex];
+        if (bucket == NULL) {
+            bucket = mBuckets[bucketIndex] = new T[BUCKET_SIZE]();
+        }
+        return bucket[0x0f & static_cast<uint8_t>(index)];
+    }
+
+    bool set(size_t index, const T& value) {
+        if (index >= size()) {
+            return false;
+        }
+
+        editItemAt(index) = value;
+        return true;
+    }
+
+private:
+    enum { NUM_BUCKETS = 16, BUCKET_SIZE = 16 };
+
+    T*  mBuckets[NUM_BUCKETS];
+    T   mDefault;
+};
+
+} // namespace android
+
+#endif // __BYTE_BUCKET_ARRAY_H
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 4d8e512..e612c0a 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -237,6 +237,7 @@
 #define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF))
 
 #define Res_MAXPACKAGE 255
+#define Res_MAXTYPE 255
 
 /**
  * Representation of a value in a resource, supplying type
@@ -510,6 +511,23 @@
     uint32_t                    mStylePoolSize;    // number of uint32_t
 };
 
+/**
+ * Wrapper class that allows the caller to retrieve a string from
+ * a string pool without knowing which string pool to look.
+ */
+class StringPoolRef {
+public:
+    StringPoolRef();
+    StringPoolRef(const ResStringPool* pool, uint32_t index);
+
+    const char* string8(size_t* outLen) const;
+    const char16_t* string16(size_t* outLen) const;
+
+private:
+    const ResStringPool*        mPool;
+    uint32_t                    mIndex;
+};
+
 /** ********************************************************************
  *  XML Tree
  *
@@ -835,6 +853,8 @@
 
     // Last index into keyStrings that is for public use by others.
     uint32_t lastPublicKey;
+
+    uint32_t typeIdOffset;
 };
 
 // The most specific locale can consist of:
@@ -1469,9 +1489,13 @@
              bool copyData=false);
     ~ResTable();
 
-    status_t add(Asset* asset, const int32_t cookie, bool copyData,
-                 const void* idmap = NULL);
-    status_t add(const void *data, size_t size);
+    status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false);
+    status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
+            const int32_t cookie=-1, bool copyData=false);
+
+    status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false);
+    status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false);
+
     status_t add(ResTable* src);
     status_t addEmpty(const int32_t cookie);
 
@@ -1610,13 +1634,14 @@
             uint32_t typeSpecFlags;
             Res_value value;
         };
+
         struct type_info {
             size_t numEntries;
             theme_entry* entries;
         };
+
         struct package_info {
-            size_t numTypes;
-            type_info types[];
+            type_info types[Res_MAXTYPE + 1];
         };
 
         void free_package(package_info* pi);
@@ -1711,6 +1736,7 @@
     size_t getBasePackageCount() const;
     const String16 getBasePackageName(size_t idx) const;
     uint32_t getBasePackageId(size_t idx) const;
+    uint32_t getLastTypeIdForPackage(size_t idx) const;
 
     // Return the number of resource tables that the object contains.
     size_t getTableCount() const;
@@ -1740,13 +1766,15 @@
             void** outData, size_t* outSize) const;
 
     enum {
-        IDMAP_HEADER_SIZE_BYTES = 3 * sizeof(uint32_t) + 2 * 256,
+        IDMAP_HEADER_SIZE_BYTES = 4 * sizeof(uint32_t) + 2 * 256,
     };
+
     // Retrieve idmap meta-data.
     //
     // This function only requires the idmap header (the first
     // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file.
     static bool getIdmapInfo(const void* idmap, size_t size,
+            uint32_t* pVersion,
             uint32_t* pTargetCrc, uint32_t* pOverlayCrc,
             String8* pTargetPath, String8* pOverlayPath);
 
@@ -1756,21 +1784,24 @@
 private:
     struct Header;
     struct Type;
+    struct Entry;
     struct Package;
     struct PackageGroup;
     struct bag_set;
+    typedef Vector<Type*> TypeList;
 
-    status_t addInternal(const void* data, size_t size, const int32_t cookie,
-                 bool copyData, const Asset* idmap);
+    status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
+            const int32_t cookie, bool copyData);
 
     ssize_t getResourcePackageIndex(uint32_t resID) const;
-    ssize_t getEntry(
-        const Package* package, int typeIndex, int entryIndex,
+
+    status_t getEntry(
+        const PackageGroup* packageGroup, int typeIndex, int entryIndex,
         const ResTable_config* config,
-        const ResTable_type** outType, const ResTable_entry** outEntry,
-        const Type** outTypeClass) const;
+        Entry* outEntry) const;
+
     status_t parsePackage(
-        const ResTable_package* const pkg, const Header* const header, uint32_t idmap_id);
+        const ResTable_package* const pkg, const Header* const header);
 
     void print_value(const Package* pkg, const Res_value& value) const;
     
diff --git a/include/androidfw/TypeWrappers.h b/include/androidfw/TypeWrappers.h
new file mode 100644
index 0000000..7bdf8af
--- /dev/null
+++ b/include/androidfw/TypeWrappers.h
@@ -0,0 +1,81 @@
+/*
+ * 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 __TYPE_WRAPPERS_H
+#define __TYPE_WRAPPERS_H
+
+#include <androidfw/ResourceTypes.h>
+
+namespace android {
+
+struct TypeVariant {
+    TypeVariant(const ResTable_type* data)
+        : data(data) {}
+
+    class iterator {
+    public:
+        iterator& operator=(const iterator& rhs) {
+            mTypeVariant = rhs.mTypeVariant;
+            mIndex = rhs.mIndex;
+        }
+
+        bool operator==(const iterator& rhs) const {
+            return mTypeVariant == rhs.mTypeVariant && mIndex == rhs.mIndex;
+        }
+
+        bool operator!=(const iterator& rhs) const {
+            return mTypeVariant != rhs.mTypeVariant || mIndex != rhs.mIndex;
+        }
+
+        iterator operator++(int) {
+            uint32_t prevIndex = mIndex;
+            operator++();
+            return iterator(mTypeVariant, prevIndex);
+        }
+
+        const ResTable_entry* operator->() const {
+            return operator*();
+        }
+
+        uint32_t index() const {
+            return mIndex;
+        }
+
+        iterator& operator++();
+        const ResTable_entry* operator*() const;
+
+    private:
+        friend struct TypeVariant;
+        iterator(const TypeVariant* tv, uint32_t index)
+            : mTypeVariant(tv), mIndex(index) {}
+        const TypeVariant* mTypeVariant;
+        uint32_t mIndex;
+    };
+
+    iterator beginEntries() const {
+        return iterator(this, 0);
+    }
+
+    iterator endEntries() const {
+        return iterator(this, dtohl(data->entryCount));
+    }
+
+    const ResTable_type* data;
+};
+
+} // namespace android
+
+#endif // __TYPE_WRAPPERS_H
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 9d6d76e..0da2b99 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -23,7 +23,9 @@
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.security.InvalidKeyException;
@@ -437,6 +439,14 @@
      * Caller should call unbindService on the result when finished.
      */
     public static KeyChainConnection bind(Context context) throws InterruptedException {
+        return bindAsUser(context, Process.myUserHandle());
+    }
+
+    /**
+     * @hide
+     */
+    public static KeyChainConnection bindAsUser(Context context, UserHandle user)
+            throws InterruptedException {
         if (context == null) {
             throw new NullPointerException("context == null");
         }
@@ -459,9 +469,10 @@
         Intent intent = new Intent(IKeyChainService.class.getName());
         ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
         intent.setComponent(comp);
-        boolean isBound = context.bindService(intent,
-                                              keyChainServiceConnection,
-                                              Context.BIND_AUTO_CREATE);
+        boolean isBound = context.bindServiceAsUser(intent,
+                                                    keyChainServiceConnection,
+                                                    Context.BIND_AUTO_CREATE,
+                                                    user);
         if (!isBound) {
             throw new AssertionError("could not bind to KeyChainService");
         }
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/androidfw/Android.mk b/libs/androidfw/Android.mk
index d21197e..957809d 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -25,6 +25,7 @@
     ObbFile.cpp \
     ResourceTypes.cpp \
     StreamingZipInflater.cpp \
+    TypeWrappers.cpp \
     ZipFileRO.cpp \
     ZipUtils.cpp
 
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 91dda75..0340928 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -256,7 +256,7 @@
     String8 targetPath;
     String8 overlayPath;
     if (!ResTable::getIdmapInfo(idmap->getBuffer(false), idmap->getLength(),
-                NULL, NULL, &targetPath, &overlayPath)) {
+                NULL, NULL, NULL, &targetPath, &overlayPath)) {
         ALOGW("failed to read idmap file %s\n", idmapPath.string());
         delete idmap;
         return false;
@@ -311,7 +311,7 @@
             ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
             return false;
         }
-        tables[i].add(ass, 1, false /* copyData */, NULL /* idMap */);
+        tables[i].add(ass);
     }
 
     return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
@@ -617,7 +617,7 @@
                     // can quickly copy it out for others.
                     ALOGV("Creating shared resources for %s", ap.path.string());
                     sharedRes = new ResTable();
-                    sharedRes->add(ass, i + 1, false, idmap);
+                    sharedRes->add(ass, idmap, i + 1, false);
 #ifdef HAVE_ANDROID_OS
                     const char* data = getenv("ANDROID_DATA");
                     LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
@@ -646,7 +646,7 @@
                 mResources->add(sharedRes);
             } else {
                 ALOGV("Parsing resources for %s", ap.path.string());
-                mResources->add(ass, i + 1, !shared, idmap);
+                mResources->add(ass, idmap, i + 1, !shared);
             }
             onlyEmptyResources = false;
 
@@ -654,7 +654,7 @@
                 delete ass;
             }
         } else {
-            ALOGW("Installing empty resources in to table %p\n", mResources);
+            ALOGV("Installing empty resources in to table %p\n", mResources);
             mResources->addEmpty(i + 1);
         }
 
@@ -736,7 +736,7 @@
         if (oass != NULL) {
             Asset* oidmap = openIdmapLocked(oap);
             offset++;
-            sharedRes->add(oass, offset + 1, false, oidmap);
+            sharedRes->add(oass, oidmap, offset + 1, false);
             const_cast<AssetManager*>(this)->mAssetPaths.add(oap);
             const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
         }
@@ -1768,6 +1768,7 @@
     }
 
     mergeInfoLocked(pMergedInfo, pContents);
+    delete pContents;
     return true;
 }
 
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a4b78a6..2e3abb5 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -17,7 +17,9 @@
 #define LOG_TAG "ResourceType"
 //#define LOG_NDEBUG 0
 
+#include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
 #include <utils/Atomic.h>
 #include <utils/ByteOrder.h>
 #include <utils/Debug.h>
@@ -30,6 +32,7 @@
 #include <memory.h>
 #include <ctype.h>
 #include <stdint.h>
+#include <stddef.h>
 
 #ifndef INT32_MAX
 #define INT32_MAX ((int32_t)(2147483647))
@@ -42,7 +45,7 @@
 #define TABLE_SUPER_NOISY(x) //x
 #define LOAD_TABLE_NOISY(x) //x
 #define TABLE_THEME(x) //x
-#define LIB_NOISY(x) x
+#define LIB_NOISY(x) //x
 
 namespace android {
 
@@ -63,9 +66,8 @@
 #endif
 #endif
 
-#define IDMAP_MAGIC         0x706d6469
-// size measured in sizeof(uint32_t)
-#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
+#define IDMAP_MAGIC             0x504D4449
+#define IDMAP_CURRENT_VERSION   0x00000001
 
 #define APP_PACKAGE_ID      0x7f
 #define SYS_PACKAGE_ID      0x01
@@ -77,6 +79,11 @@
     return (c < 0x0080 && isspace(c));
 }
 
+template<typename T>
+inline static T max(T a, T b) {
+    return a > b ? a : b;
+}
+
 // range checked; guaranteed to NUL-terminate within the stated number of available slots
 // NOTE: if this truncates the dst string due to running out of space, no attempt is
 // made to avoid splitting surrogate pairs.
@@ -215,104 +222,179 @@
     fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
 }
 
-static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
-{
-    if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
-        ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes);
+static bool assertIdmapHeader(const void* idmap, size_t size) {
+    if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
+        ALOGE("idmap: header is not word aligned");
         return false;
     }
-    if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
-        ALOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n",
-             *map, htodl(IDMAP_MAGIC));
+
+    if (size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+        ALOGW("idmap: header too small (%d bytes)", (uint32_t) size);
+        return false;
+    }
+
+    const uint32_t magic = htodl(*reinterpret_cast<const uint32_t*>(idmap));
+    if (magic != IDMAP_MAGIC) {
+        ALOGW("idmap: no magic found in header (is 0x%08x, expected 0x%08x)",
+             magic, IDMAP_MAGIC);
+        return false;
+    }
+
+    const uint32_t version = htodl(*(reinterpret_cast<const uint32_t*>(idmap) + 1));
+    if (version != IDMAP_CURRENT_VERSION) {
+        // We are strict about versions because files with this format are
+        // auto-generated and don't need backwards compatibility.
+        ALOGW("idmap: version mismatch in header (is 0x%08x, expected 0x%08x)",
+                version, IDMAP_CURRENT_VERSION);
         return false;
     }
     return true;
 }
 
-static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue)
-{
-    // see README for details on the format of map
-    if (!assertIdmapHeader(map, sizeBytes)) {
-        return UNKNOWN_ERROR;
-    }
-    map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment
-    // size of data block, in uint32_t
-    const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t);
-    const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id
-    const uint32_t entry = Res_GETENTRY(key);
-    const uint32_t typeCount = *map;
+class IdmapEntries {
+public:
+    IdmapEntries() : mData(NULL) {}
 
-    if (type > typeCount) {
-        ALOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount);
-        return UNKNOWN_ERROR;
-    }
-    if (typeCount > size) {
-        ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size);
-        return UNKNOWN_ERROR;
-    }
-    const uint32_t typeOffset = map[type];
-    if (typeOffset == 0) {
-        *outValue = 0;
-        return NO_ERROR;
-    }
-    if (typeOffset + 1 > size) {
-        ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
-             typeOffset, (int)size);
-        return UNKNOWN_ERROR;
-    }
-    const uint32_t entryCount = map[typeOffset];
-    const uint32_t entryOffset = map[typeOffset + 1];
-    if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) {
-        *outValue = 0;
-        return NO_ERROR;
-    }
-    const uint32_t index = typeOffset + 2 + entry - entryOffset;
-    if (index > size) {
-        ALOGW("Resource ID map: entry index=%u exceeds size of map=%d\n", index, (int)size);
-        *outValue = 0;
-        return NO_ERROR;
-    }
-    *outValue = map[index];
+    bool hasEntries() const {
+        if (mData == NULL) {
+            return false;
+        }
 
-    return NO_ERROR;
-}
+        return (dtohs(*mData) > 0);
+    }
 
-static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId)
-{
-    if (!assertIdmapHeader(map, mapSize)) {
-        return UNKNOWN_ERROR;
+    size_t byteSize() const {
+        if (mData == NULL) {
+            return 0;
+        }
+        uint16_t entryCount = dtohs(mData[2]);
+        return (sizeof(uint16_t) * 4) + (sizeof(uint32_t) * static_cast<size_t>(entryCount));
     }
-    if (mapSize <= IDMAP_HEADER_SIZE + 1) {
-        ALOGW("corrupt idmap: map size %d too short\n", (int)mapSize);
-        return UNKNOWN_ERROR;
+
+    uint8_t targetTypeId() const {
+        if (mData == NULL) {
+            return 0;
+        }
+        return dtohs(mData[0]);
     }
-    uint32_t typeCount = *(map + IDMAP_HEADER_SIZE);
-    if (typeCount == 0) {
-        ALOGW("corrupt idmap: no types\n");
-        return UNKNOWN_ERROR;
+
+    uint8_t overlayTypeId() const {
+        if (mData == NULL) {
+            return 0;
+        }
+        return dtohs(mData[1]);
     }
-    if (IDMAP_HEADER_SIZE + 1 + typeCount > mapSize) {
-        ALOGW("corrupt idmap: number of types %u extends past idmap size %d\n", typeCount, (int)mapSize);
-        return UNKNOWN_ERROR;
-    }
-    const uint32_t* p = map + IDMAP_HEADER_SIZE + 1;
-    // find first defined type
-    while (*p == 0) {
-        ++p;
-        if (--typeCount == 0) {
-            ALOGW("corrupt idmap: types declared, none found\n");
+
+    status_t setTo(const void* entryHeader, size_t size) {
+        if (reinterpret_cast<uintptr_t>(entryHeader) & 0x03) {
+            ALOGE("idmap: entry header is not word aligned");
             return UNKNOWN_ERROR;
         }
+
+        if (size < sizeof(uint16_t) * 4) {
+            ALOGE("idmap: entry header is too small (%u bytes)", (uint32_t) size);
+            return UNKNOWN_ERROR;
+        }
+
+        const uint16_t* header = reinterpret_cast<const uint16_t*>(entryHeader);
+        const uint16_t targetTypeId = dtohs(header[0]);
+        const uint16_t overlayTypeId = dtohs(header[1]);
+        if (targetTypeId == 0 || overlayTypeId == 0 || targetTypeId > 255 || overlayTypeId > 255) {
+            ALOGE("idmap: invalid type map (%u -> %u)", targetTypeId, overlayTypeId);
+            return UNKNOWN_ERROR;
+        }
+
+        uint16_t entryCount = dtohs(header[2]);
+        if (size < sizeof(uint32_t) * (entryCount + 2)) {
+            ALOGE("idmap: too small (%u bytes) for the number of entries (%u)",
+                    (uint32_t) size, (uint32_t) entryCount);
+            return UNKNOWN_ERROR;
+        }
+        mData = header;
+        return NO_ERROR;
     }
 
-    // determine package id from first entry of first type
-    const uint32_t offset = *p + IDMAP_HEADER_SIZE + 2;
-    if (offset > mapSize) {
-        ALOGW("corrupt idmap: entry offset %u points outside map size %d\n", offset, (int)mapSize);
+    status_t lookup(uint16_t entryId, uint16_t* outEntryId) const {
+        uint16_t entryCount = dtohs(mData[2]);
+        uint16_t offset = dtohs(mData[3]);
+
+        if (entryId < offset) {
+            // The entry is not present in this idmap
+            return BAD_INDEX;
+        }
+
+        entryId -= offset;
+
+        if (entryId >= entryCount) {
+            // The entry is not present in this idmap
+            return BAD_INDEX;
+        }
+
+        // It is safe to access the type here without checking the size because
+        // we have checked this when it was first loaded.
+        const uint32_t* entries = reinterpret_cast<const uint32_t*>(mData) + 2;
+        uint32_t mappedEntry = dtohl(entries[entryId]);
+        if (mappedEntry == 0xffffffff) {
+            // This entry is not present in this idmap
+            return BAD_INDEX;
+        }
+        *outEntryId = static_cast<uint16_t>(mappedEntry);
+        return NO_ERROR;
+    }
+
+private:
+    const uint16_t* mData;
+};
+
+status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, KeyedVector<uint8_t, IdmapEntries>* outMap) {
+    if (!assertIdmapHeader(idmap, size)) {
         return UNKNOWN_ERROR;
     }
-    *outId = (map[offset] >> 24) & 0x000000ff;
 
+    size -= ResTable::IDMAP_HEADER_SIZE_BYTES;
+    if (size < sizeof(uint16_t) * 2) {
+        ALOGE("idmap: too small to contain any mapping");
+        return UNKNOWN_ERROR;
+    }
+
+    const uint16_t* data = reinterpret_cast<const uint16_t*>(
+            reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES);
+
+    uint16_t targetPackageId = dtohs(*(data++));
+    if (targetPackageId == 0 || targetPackageId > 255) {
+        ALOGE("idmap: target package ID is invalid (%02x)", targetPackageId);
+        return UNKNOWN_ERROR;
+    }
+
+    uint16_t mapCount = dtohs(*(data++));
+    if (mapCount == 0) {
+        ALOGE("idmap: no mappings");
+        return UNKNOWN_ERROR;
+    }
+
+    if (mapCount > 255) {
+        ALOGW("idmap: too many mappings. Only 255 are possible but %u are present", (uint32_t) mapCount);
+    }
+
+    while (size > sizeof(uint16_t) * 4) {
+        IdmapEntries entries;
+        status_t err = entries.setTo(data, size);
+        if (err != NO_ERROR) {
+            return err;
+        }
+
+        ssize_t index = outMap->add(entries.overlayTypeId(), entries);
+        if (index < 0) {
+            return NO_MEMORY;
+        }
+
+        data += entries.byteSize() / sizeof(uint16_t);
+        size -= entries.byteSize();
+    }
+
+    if (outPackageId != NULL) {
+        *outPackageId = static_cast<uint8_t>(targetPackageId);
+    }
     return NO_ERROR;
 }
 
@@ -2726,7 +2808,7 @@
         free(resourceIDMap);
     }
 
-    ResTable* const                 owner;
+    const ResTable* const           owner;
     void*                           ownedData;
     const ResTable_header*          header;
     size_t                          size;
@@ -2739,6 +2821,17 @@
     size_t                          resourceIDMapSize;
 };
 
+struct ResTable::Entry {
+    ResTable_config config;
+    const ResTable_entry* entry;
+    const ResTable_type* type;
+    uint32_t specFlags;
+    const Package* package;
+
+    StringPoolRef typeStr;
+    StringPoolRef keyStr;
+};
+
 struct ResTable::Type
 {
     Type(const Header* _header, const Package* _package, size_t count)
@@ -2749,33 +2842,29 @@
     const size_t                    entryCount;
     const ResTable_typeSpec*        typeSpec;
     const uint32_t*                 typeSpecFlags;
+    IdmapEntries                    idmapEntries;
     Vector<const ResTable_type*>    configs;
 };
 
 struct ResTable::Package
 {
     Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
-        : owner(_owner), header(_header), package(_package) { }
-    ~Package()
-    {
-        size_t i = types.size();
-        while (i > 0) {
-            i--;
-            delete types[i];
+        : owner(_owner), header(_header), package(_package), typeIdOffset(0) {
+        if (dtohs(package->header.headerSize) == sizeof(package)) {
+            // The package structure is the same size as the definition.
+            // This means it contains the typeIdOffset field.
+            typeIdOffset = package->typeIdOffset;
         }
     }
 
-    ResTable* const                 owner;
+    const ResTable* const           owner;
     const Header* const             header;
     const ResTable_package* const   package;
-    Vector<Type*>                   types;
 
     ResStringPool                   typeStrings;
     ResStringPool                   keyStrings;
 
-    const Type* getType(size_t idx) const {
-        return idx < types.size() ? types[idx] : NULL;
-    }
+    size_t                          typeIdOffset;
 };
 
 // A group of objects describing a particular resource package.
@@ -2787,13 +2876,24 @@
         : owner(_owner)
         , name(_name)
         , id(_id)
-        , typeCount(0)
+        , largestTypeId(0)
         , bags(NULL)
         , dynamicRefTable(static_cast<uint8_t>(_id))
     { }
 
     ~PackageGroup() {
         clearBagCache();
+        const size_t numTypes = types.size();
+        for (size_t i = 0; i < numTypes; i++) {
+            const TypeList& typeList = types[i];
+            const size_t numInnerTypes = typeList.size();
+            for (size_t j = 0; j < numInnerTypes; j++) {
+                if (typeList[j]->package->owner == owner) {
+                    delete typeList[j];
+                }
+            }
+        }
+
         const size_t N = packages.size();
         for (size_t i=0; i<N; i++) {
             Package* pkg = packages[i];
@@ -2806,17 +2906,15 @@
     void clearBagCache() {
         if (bags) {
             TABLE_NOISY(printf("bags=%p\n", bags));
-            Package* pkg = packages[0];
-            TABLE_NOISY(printf("typeCount=%x\n", typeCount));
-            for (size_t i=0; i<typeCount; i++) {
+            for (size_t i = 0; i < bags->size(); i++) {
                 TABLE_NOISY(printf("type=%d\n", i));
-                const Type* type = pkg->getType(i);
-                if (type != NULL) {
-                    bag_set** typeBags = bags[i];
+                const TypeList& typeList = types[i];
+                if (typeList.isEmpty()) {
+                    bag_set** typeBags = bags->get(i);
                     TABLE_NOISY(printf("typeBags=%p\n", typeBags));
                     if (typeBags) {
-                        TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount));
-                        const size_t N = type->entryCount;
+                        const size_t N = typeList[0]->entryCount;
+                        TABLE_NOISY(printf("type->entryCount=%x\n", N));
                         for (size_t j=0; j<N; j++) {
                             if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF)
                                 free(typeBags[j]);
@@ -2825,25 +2923,38 @@
                     }
                 }
             }
-            free(bags);
+            delete bags;
             bags = NULL;
         }
     }
 
-    ResTable* const                 owner;
+    ssize_t findType16(const char16_t* type, size_t len) const {
+        const size_t N = packages.size();
+        for (size_t i = 0; i < N; i++) {
+            ssize_t index = packages[i]->typeStrings.indexOfString(type, len);
+            if (index >= 0) {
+                return index + packages[i]->typeIdOffset;
+            }
+        }
+        return -1;
+    }
+
+    const ResTable* const           owner;
     String16 const                  name;
     uint32_t const                  id;
+
+    // This is mainly used to keep track of the loaded packages
+    // and to clean them up properly. Accessing resources happens from
+    // the 'types' array.
     Vector<Package*>                packages;
 
-    // This is for finding typeStrings and other common package stuff.
-    Package*                        basePackage;
+    ByteBucketArray<TypeList>       types;
 
-    // For quick access.
-    size_t                          typeCount;
+    uint8_t                         largestTypeId;
 
     // Computed attribute bags, first indexed by the type and second
     // by the entry in that type.
-    bag_set***                      bags;
+    ByteBucketArray<bag_set**>*     bags;
 
     // The table mapping dynamic references to resolved references for
     // this package group.
@@ -2879,7 +2990,7 @@
 
 void ResTable::Theme::free_package(package_info* pi)
 {
-    for (size_t j=0; j<pi->numTypes; j++) {
+    for (size_t j = 0; j <= Res_MAXTYPE; j++) {
         theme_entry* te = pi->types[j].entries;
         if (te != NULL) {
             free(te);
@@ -2890,10 +3001,8 @@
 
 ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi)
 {
-    package_info* newpi = (package_info*)malloc(
-        sizeof(package_info) + (pi->numTypes*sizeof(type_info)));
-    newpi->numTypes = pi->numTypes;
-    for (size_t j=0; j<newpi->numTypes; j++) {
+    package_info* newpi = (package_info*)malloc(sizeof(package_info));
+    for (size_t j = 0; j <= Res_MAXTYPE; j++) {
         size_t cnt = pi->types[j].numEntries;
         newpi->types[j].numEntries = cnt;
         theme_entry* te = pi->types[j].entries;
@@ -2946,17 +3055,14 @@
             curPI = mPackages[pidx];
             if (curPI == NULL) {
                 PackageGroup* const grp = mTable.mPackageGroups[pidx];
-                int cnt = grp->typeCount;
-                curPI = (package_info*)malloc(
-                    sizeof(package_info) + (cnt*sizeof(type_info)));
-                curPI->numTypes = cnt;
-                memset(curPI->types, 0, cnt*sizeof(type_info));
+                curPI = (package_info*)malloc(sizeof(package_info));
+                memset(curPI, 0, sizeof(*curPI));
                 mPackages[pidx] = curPI;
             }
             curType = 0xffffffff;
         }
         if (curType != t) {
-            if (t >= curPI->numTypes) {
+            if (t > Res_MAXTYPE) {
                 ALOGE("Style contains key with bad type: 0x%08x\n", attrRes);
                 bag++;
                 continue;
@@ -2965,8 +3071,8 @@
             curEntries = curPI->types[t].entries;
             if (curEntries == NULL) {
                 PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex];
-                const Type* type = grp->packages[0]->getType(t);
-                int cnt = type != NULL ? type->entryCount : 0;
+                const TypeList& typeList = grp->types[t];
+                int cnt = typeList.isEmpty() ? 0 : typeList[0]->entryCount;
                 curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry));
                 memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry));
                 curPI->types[t].numEntries = cnt;
@@ -2981,8 +3087,8 @@
         }
         theme_entry* curEntry = curEntries + e;
         TABLE_NOISY(ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
-                   attrRes, bag->map.value.dataType, bag->map.value.data,
-             curEntry->value.dataType));
+                attrRes, bag->map.value.dataType, bag->map.value.data,
+                curEntry->value.dataType));
         if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
             curEntry->stringBlock = bag->stringBlock;
             curEntry->typeSpecFlags |= bagTypeSpecFlags;
@@ -3057,8 +3163,8 @@
             const package_info* const pi = mPackages[p];
             TABLE_THEME(ALOGI("Found package: %p", pi));
             if (pi != NULL) {
-                TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, pi->numTypes));
-                if (t < pi->numTypes) {
+                TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, Res_MAXTYPE + 1));
+                if (t <= Res_MAXTYPE) {
                     const type_info& ti = pi->types[t];
                     TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries));
                     if (e < ti.numEntries) {
@@ -3120,14 +3226,13 @@
         package_info* pi = mPackages[i];
         if (pi == NULL) continue;
 
-        ALOGI("  Package #0x%02x:\n", (int)(i+1));
-        for (size_t j=0; j<pi->numTypes; j++) {
+        ALOGI("  Package #0x%02x:\n", (int)(i + 1));
+        for (size_t j = 0; j <= Res_MAXTYPE; j++) {
             type_info& ti = pi->types[j];
             if (ti.numEntries == 0) continue;
-
-            ALOGI("    Type #0x%02x:\n", (int)(j+1));
-            for (size_t k=0; k<ti.numEntries; k++) {
-                theme_entry& te = ti.entries[k];
+            ALOGI("    Type #0x%02x:\n", (int)(j + 1));
+            for (size_t k = 0; k < ti.numEntries; k++) {
+                const theme_entry& te = ti.entries[k];
                 if (te.value.dataType == Res_value::TYPE_NULL) continue;
                 ALOGI("      0x%08x: t=0x%x, d=0x%08x (block=%d)\n",
                      (int)Res_MAKEID(i, j, k),
@@ -3150,7 +3255,7 @@
 {
     memset(&mParams, 0, sizeof(mParams));
     memset(mPackageMap, 0, sizeof(mPackageMap));
-    addInternal(data, size, cookie, copyData, NULL /* idMap */);
+    addInternal(data, size, NULL, 0, cookie, copyData);
     LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
     //ALOGI("Creating ResTable %p\n", this);
 }
@@ -3166,21 +3271,45 @@
     return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
 }
 
-status_t ResTable::add(const void* data, size_t size) {
-    return addInternal(data, size, 0 /* cookie */,
-            false /* copyData */, NULL /* idMap */);
+status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) {
+    return addInternal(data, size, NULL, 0, cookie, copyData);
 }
 
-status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData, const void* idmap)
-{
+status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
+        const int32_t cookie, bool copyData) {
+    return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData);
+}
+
+status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
     const void* data = asset->getBuffer(true);
     if (data == NULL) {
         ALOGW("Unable to get buffer of resource asset file");
         return UNKNOWN_ERROR;
     }
-    size_t size = (size_t)asset->getLength();
-    return addInternal(data, size, cookie, copyData,
-            reinterpret_cast<const Asset*>(idmap));
+
+    return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData);
+}
+
+status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
+    const void* data = asset->getBuffer(true);
+    if (data == NULL) {
+        ALOGW("Unable to get buffer of resource asset file");
+        return UNKNOWN_ERROR;
+    }
+
+    size_t idmapSize = 0;
+    const void* idmapData = NULL;
+    if (idmapAsset != NULL) {
+        idmapData = idmapAsset->getBuffer(true);
+        if (idmapData == NULL) {
+            ALOGW("Unable to get buffer of idmap asset file");
+            return UNKNOWN_ERROR;
+        }
+        idmapSize = static_cast<size_t>(idmapAsset->getLength());
+    }
+
+    return addInternal(data, static_cast<size_t>(asset->getLength()),
+            idmapData, idmapSize, cookie, copyData);
 }
 
 status_t ResTable::add(ResTable* src)
@@ -3197,8 +3326,16 @@
         for (size_t j=0; j<srcPg->packages.size(); j++) {
             pg->packages.add(srcPg->packages[j]);
         }
-        pg->basePackage = srcPg->basePackage;
-        pg->typeCount = srcPg->typeCount;
+
+        for (size_t j = 0; j < srcPg->types.size(); j++) {
+            if (srcPg->types[j].isEmpty()) {
+                continue;
+            }
+
+            TypeList& typeList = pg->types.editItemAt(j);
+            typeList.appendVector(srcPg->types[j]);
+        }
+        pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
         mPackageGroups.add(pg);
     }
 
@@ -3224,38 +3361,39 @@
     return (mError=NO_ERROR);
 }
 
-status_t ResTable::addInternal(const void* data, size_t size, const int32_t cookie,
-                       bool copyData, const Asset* idmap)
+status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
+        const int32_t cookie, bool copyData)
 {
-    if (!data) return NO_ERROR;
+    if (!data) {
+        return NO_ERROR;
+    }
+
     Header* header = new Header(this);
     header->index = mHeaders.size();
     header->cookie = cookie;
-    if (idmap != NULL) {
-        const size_t idmap_size = idmap->getLength();
-        const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true);
-        header->resourceIDMap = (uint32_t*)malloc(idmap_size);
+    if (idmapData != NULL) {
+        header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
         if (header->resourceIDMap == NULL) {
             delete header;
             return (mError = NO_MEMORY);
         }
-        memcpy((void*)header->resourceIDMap, idmap_data, idmap_size);
-        header->resourceIDMapSize = idmap_size;
+        memcpy(header->resourceIDMap, idmapData, idmapDataSize);
+        header->resourceIDMapSize = idmapDataSize;
     }
     mHeaders.add(header);
 
     const bool notDeviceEndian = htods(0xf0) != 0xf0;
 
     LOAD_TABLE_NOISY(
-        ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, asset=%p, copy=%d "
-             "idmap=%p\n", data, size, cookie, asset, copyData, idmap));
+        ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, copy=%d "
+             "idmap=%p\n", data, dataSize, cookie, copyData, idmap));
 
     if (copyData || notDeviceEndian) {
-        header->ownedData = malloc(size);
+        header->ownedData = malloc(dataSize);
         if (header->ownedData == NULL) {
             return (mError=NO_MEMORY);
         }
-        memcpy(header->ownedData, data, size);
+        memcpy(header->ownedData, data, dataSize);
         data = header->ownedData;
     }
 
@@ -3265,10 +3403,10 @@
     //     dtohl(header->header->header.size), header->header->header.size);
     LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header));
     if (dtohs(header->header->header.headerSize) > header->size
-            || header->size > size) {
+            || header->size > dataSize) {
         ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
              (int)dtohs(header->header->header.headerSize),
-             (int)header->size, (int)size);
+             (int)header->size, (int)dataSize);
         return (mError=BAD_TYPE);
     }
     if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
@@ -3313,16 +3451,8 @@
                      dtohl(header->header->packageCount));
                 return (mError=BAD_TYPE);
             }
-            uint32_t idmap_id = 0;
-            if (idmap != NULL) {
-                uint32_t tmp;
-                if (getIdmapPackageId(header->resourceIDMap,
-                                      header->resourceIDMapSize,
-                                      &tmp) == NO_ERROR) {
-                    idmap_id = tmp;
-                }
-            }
-            if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) {
+
+            if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
                 return mError;
             }
             curPackage++;
@@ -3405,46 +3535,38 @@
         ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
         return false;
     }
-    if (grp->packages.size() > 0) {
-        const Package* const package = grp->packages[0];
 
-        const ResTable_type* type;
-        const ResTable_entry* entry;
-        ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL);
-        if (offset <= 0) {
-            return false;
-        }
-
-        outName->package = grp->name.string();
-        outName->packageLen = grp->name.size();
-        if (allowUtf8) {
-            outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
-            outName->name8 = grp->basePackage->keyStrings.string8At(
-                dtohl(entry->key.index), &outName->nameLen);
-        } else {
-            outName->type8 = NULL;
-            outName->name8 = NULL;
-        }
-        if (outName->type8 == NULL) {
-            outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
-            // If we have a bad index for some reason, we should abort.
-            if (outName->type == NULL) {
-                return false;
-            }
-        }
-        if (outName->name8 == NULL) {
-            outName->name = grp->basePackage->keyStrings.stringAt(
-                dtohl(entry->key.index), &outName->nameLen);
-            // If we have a bad index for some reason, we should abort.
-            if (outName->name == NULL) {
-                return false;
-            }
-        }
-
-        return true;
+    Entry entry;
+    status_t err = getEntry(grp, t, e, NULL, &entry);
+    if (err != NO_ERROR) {
+        return false;
     }
 
-    return false;
+    outName->package = grp->name.string();
+    outName->packageLen = grp->name.size();
+    if (allowUtf8) {
+        outName->type8 = entry.typeStr.string8(&outName->typeLen);
+        outName->name8 = entry.keyStr.string8(&outName->nameLen);
+    } else {
+        outName->type8 = NULL;
+        outName->name8 = NULL;
+    }
+    if (outName->type8 == NULL) {
+        outName->type = entry.typeStr.string16(&outName->typeLen);
+        // If we have a bad index for some reason, we should abort.
+        if (outName->type == NULL) {
+            return false;
+        }
+    }
+    if (outName->name8 == NULL) {
+        outName->name = entry.keyStr.string16(&outName->nameLen);
+        // If we have a bad index for some reason, we should abort.
+        if (outName->name == NULL) {
+            return false;
+        }
+    }
+
+    return true;
 }
 
 ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
@@ -3471,15 +3593,6 @@
         return BAD_INDEX;
     }
 
-    const Res_value* bestValue = NULL;
-    const Package* bestPackage = NULL;
-    ResTable_config bestItem;
-    memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
-
-    if (outSpecFlags != NULL) *outSpecFlags = 0;
-
-    // Look through all resource packages, starting with the most
-    // recently added.
     const PackageGroup* const grp = mPackageGroups[p];
     if (grp == NULL) {
         ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
@@ -3487,142 +3600,62 @@
     }
 
     // Allow overriding density
-    const ResTable_config* desiredConfig = &mParams;
-    ResTable_config* overrideConfig = NULL;
+    ResTable_config desiredConfig = mParams;
     if (density > 0) {
-        overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
-        if (overrideConfig == NULL) {
-            ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
-            return BAD_INDEX;
-        }
-        memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
-        overrideConfig->density = density;
-        desiredConfig = overrideConfig;
+        desiredConfig.density = density;
     }
 
-    ssize_t rc = BAD_VALUE;
-    size_t ip = grp->packages.size();
-    while (ip > 0) {
-        ip--;
-        int T = t;
-        int E = e;
-
-        const Package* const package = grp->packages[ip];
-        if (package->header->resourceIDMap) {
-            uint32_t overlayResID = 0x0;
-            status_t retval = idmapLookup(package->header->resourceIDMap,
-                                          package->header->resourceIDMapSize,
-                                          resID, &overlayResID);
-            if (retval == NO_ERROR && overlayResID != 0x0) {
-                // for this loop iteration, this is the type and entry we really want
-                ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
-                T = Res_GETTYPE(overlayResID);
-                E = Res_GETENTRY(overlayResID);
-            } else {
-                // resource not present in overlay package, continue with the next package
-                continue;
-            }
-        }
-
-        const ResTable_type* type;
-        const ResTable_entry* entry;
-        const Type* typeClass;
-        ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
-        if (offset <= 0) {
-            // No {entry, appropriate config} pair found in package. If this
-            // package is an overlay package (ip != 0), this simply means the
-            // overlay package did not specify a default.
-            // Non-overlay packages are still required to provide a default.
-            if (offset < 0 && ip == 0) {
-                ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
-                        resID, T, E, ip, (int)offset);
-                rc = offset;
-                goto out;
-            }
-            continue;
-        }
-
-        if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
-            if (!mayBeBag) {
-                ALOGW("Requesting resource 0x%x failed because it is complex\n",
-                     resID);
-            }
-            continue;
-        }
-
-        if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
-            ALOGW("ResTable_item at %d is beyond type chunk data %d",
-                 (int)offset, dtohl(type->header.size));
-            rc = BAD_TYPE;
-            goto out;
-        }
-
-        const Res_value* item =
-            (const Res_value*)(((const uint8_t*)type) + offset);
-        ResTable_config thisConfig;
-        thisConfig.copyFromDtoH(type->config);
-
-        if (outSpecFlags != NULL) {
-            if (typeClass->typeSpecFlags != NULL) {
-                *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
-            } else {
-                *outSpecFlags = -1;
-            }
-        }
-
-        if (bestPackage != NULL &&
-            (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
-            // Discard thisConfig not only if bestItem is more specific, but also if the two configs
-            // are identical (diff == 0), or overlay packages will not take effect.
-            continue;
-        }
-
-        bestItem = thisConfig;
-        bestValue = item;
-        bestPackage = package;
+    Entry entry;
+    status_t err = getEntry(grp, t, e, &desiredConfig, &entry);
+    if (err != NO_ERROR) {
+        ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n",
+                resID, t, e, err);
+        return err;
     }
 
-    TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
-
-    if (bestValue) {
-        outValue->size = dtohs(bestValue->size);
-        outValue->res0 = bestValue->res0;
-        outValue->dataType = bestValue->dataType;
-        outValue->data = dtohl(bestValue->data);
-
-        // The reference may be pointing to a resource in a shared library. These
-        // references have build-time generated package IDs. These ids may not match
-        // the actual package IDs of the corresponding packages in this ResTable.
-        // We need to fix the package ID based on a mapping.
-        status_t err = grp->dynamicRefTable.lookupResourceValue(outValue);
-        if (err != NO_ERROR) {
-            ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
-            rc = BAD_VALUE;
-            goto out;
+    if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+        if (!mayBeBag) {
+            ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
         }
-
-        if (outConfig != NULL) {
-            *outConfig = bestItem;
-        }
-        TABLE_NOISY(size_t len;
-              printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
-                     bestPackage->header->index,
-                     outValue->dataType,
-                     outValue->dataType == bestValue->TYPE_STRING
-                     ? String8(bestPackage->header->values.stringAt(
-                         outValue->data, &len)).string()
-                     : "",
-                     outValue->data));
-        rc = bestPackage->header->index;
-        goto out;
+        return BAD_VALUE;
     }
 
-out:
-    if (overrideConfig != NULL) {
-        free(overrideConfig);
+    const Res_value* value = reinterpret_cast<const Res_value*>(
+            reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
+
+    outValue->size = dtohs(value->size);
+    outValue->res0 = value->res0;
+    outValue->dataType = value->dataType;
+    outValue->data = dtohl(value->data);
+
+    // The reference may be pointing to a resource in a shared library. These
+    // references have build-time generated package IDs. These ids may not match
+    // the actual package IDs of the corresponding packages in this ResTable.
+    // We need to fix the package ID based on a mapping.
+    if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
+        ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
+        return BAD_VALUE;
     }
 
-    return rc;
+    TABLE_NOISY(size_t len;
+          printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
+                 entry.package->header->index,
+                 outValue->dataType,
+                 outValue->dataType == Res_value::TYPE_STRING
+                 ? String8(entry.package->header->values.stringAt(
+                     outValue->data, &len)).string()
+                 : "",
+                 outValue->data));
+
+    if (outSpecFlags != NULL) {
+        *outSpecFlags = entry.specFlags;
+    }
+
+    if (outConfig != NULL) {
+        *outConfig = entry.config;
+    }
+
+    return entry.package->header->index;
 }
 
 ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
@@ -3721,29 +3754,25 @@
     PackageGroup* const grp = mPackageGroups[p];
     if (grp == NULL) {
         ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID);
-        return false;
-    }
-
-    if (t >= (int)grp->typeCount) {
-        ALOGW("Type identifier 0x%x is larger than type count 0x%x",
-             t+1, (int)grp->typeCount);
         return BAD_INDEX;
     }
 
-    const Package* const basePackage = grp->packages[0];
+    const TypeList& typeConfigs = grp->types[t];
+    if (typeConfigs.isEmpty()) {
+        ALOGW("Type identifier 0x%x does not exist.", t+1);
+        return BAD_INDEX;
+    }
 
-    const Type* const typeConfigs = basePackage->getType(t);
-
-    const size_t NENTRY = typeConfigs->entryCount;
+    const size_t NENTRY = typeConfigs[0]->entryCount;
     if (e >= (int)NENTRY) {
         ALOGW("Entry identifier 0x%x is larger than entry count 0x%x",
-             e, (int)typeConfigs->entryCount);
+             e, (int)typeConfigs[0]->entryCount);
         return BAD_INDEX;
     }
 
     // First see if we've already computed this bag...
     if (grp->bags) {
-        bag_set** typeSet = grp->bags[t];
+        bag_set** typeSet = grp->bags->get(t);
         if (typeSet) {
             bag_set* set = typeSet[e];
             if (set) {
@@ -3764,229 +3793,174 @@
 
     // Bag not found, we need to compute it!
     if (!grp->bags) {
-        grp->bags = (bag_set***)calloc(grp->typeCount, sizeof(bag_set*));
+        grp->bags = new ByteBucketArray<bag_set**>();
         if (!grp->bags) return NO_MEMORY;
     }
 
-    bag_set** typeSet = grp->bags[t];
+    bag_set** typeSet = grp->bags->get(t);
     if (!typeSet) {
         typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
         if (!typeSet) return NO_MEMORY;
-        grp->bags[t] = typeSet;
+        grp->bags->set(t, typeSet);
     }
 
     // Mark that we are currently working on this one.
     typeSet[e] = (bag_set*)0xFFFFFFFF;
 
+    TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+
+    // Now collect all bag attributes
+    Entry entry;
+    status_t err = getEntry(grp, t, e, &mParams, &entry);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    const uint16_t entrySize = dtohs(entry.entry->size);
+    const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
+        ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
+    const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
+        ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+
+    size_t N = count;
+
+    TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
+                     entrySize, parent, count));
+
+    // If this map inherits from another, we need to start
+    // with its parent's values.  Otherwise start out empty.
+    TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
+                       entrySize, parent));
+
     // This is what we are building.
     bag_set* set = NULL;
 
-    TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+    if (parent) {
+        uint32_t resolvedParent = parent;
 
-    ResTable_config bestConfig;
-    memset(&bestConfig, 0, sizeof(bestConfig));
-
-    // Now collect all bag attributes from all packages.
-    size_t ip = grp->packages.size();
-    while (ip > 0) {
-        ip--;
-        int T = t;
-        int E = e;
-
-        const Package* const package = grp->packages[ip];
-        if (package->header->resourceIDMap) {
-            uint32_t overlayResID = 0x0;
-            status_t retval = idmapLookup(package->header->resourceIDMap,
-                                          package->header->resourceIDMapSize,
-                                          resID, &overlayResID);
-            if (retval == NO_ERROR && overlayResID != 0x0) {
-                // for this loop iteration, this is the type and entry we really want
-                ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
-                T = Res_GETTYPE(overlayResID);
-                E = Res_GETENTRY(overlayResID);
-            } else {
-                // resource not present in overlay package, continue with the next package
-                continue;
-            }
+        // Bags encode a parent reference without using the standard
+        // Res_value structure. That means we must always try to
+        // resolve a parent reference in case it is actually a
+        // TYPE_DYNAMIC_REFERENCE.
+        status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent);
+        if (err != NO_ERROR) {
+            ALOGE("Failed resolving bag parent id 0x%08x", parent);
+            return UNKNOWN_ERROR;
         }
 
-        const ResTable_type* type;
-        const ResTable_entry* entry;
-        const Type* typeClass;
-        ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E);
-        ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
-        ALOGV("Resulting offset=%d\n", (int)offset);
-        if (offset <= 0) {
-            // No {entry, appropriate config} pair found in package. If this
-            // package is an overlay package (ip != 0), this simply means the
-            // overlay package did not specify a default.
-            // Non-overlay packages are still required to provide a default.
-            if (offset < 0 && ip == 0) {
-                if (set) free(set);
-                return offset;
-            }
-            continue;
+        const bag_entry* parentBag;
+        uint32_t parentTypeSpecFlags = 0;
+        const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags);
+        const size_t NT = ((NP >= 0) ? NP : 0) + N;
+        set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
+        if (set == NULL) {
+            return NO_MEMORY;
         }
-
-        if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) {
-            ALOGW("Skipping entry 0x%x in package table %zu because it is not complex!\n",
-                 resID, ip);
-            continue;
-        }
-
-        if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) {
-            continue;
-        }
-        bestConfig = type->config;
-        if (set) {
-            free(set);
-            set = NULL;
-        }
-
-        const uint16_t entrySize = dtohs(entry->size);
-        const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
-            ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0;
-        const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
-            ? dtohl(((const ResTable_map_entry*)entry)->count) : 0;
-
-        size_t N = count;
-
-        TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
-                         entrySize, parent, count));
-
-        // If this map inherits from another, we need to start
-        // with its parent's values.  Otherwise start out empty.
-        TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
-                           entrySize, parent));
-        if (parent) {
-            uint32_t resolvedParent = parent;
-
-            // Bags encode a parent reference without using the standard
-            // Res_value structure. That means we must always try to
-            // resolve a parent reference in case it is actually a
-            // TYPE_DYNAMIC_REFERENCE.
-            status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent);
-            if (err != NO_ERROR) {
-                ALOGE("Failed resolving bag parent id 0x%08x", parent);
-                return UNKNOWN_ERROR;
-            }
-
-            const bag_entry* parentBag;
-            uint32_t parentTypeSpecFlags = 0;
-            const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags);
-            const size_t NT = ((NP >= 0) ? NP : 0) + N;
-            set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
-            if (set == NULL) {
-                return NO_MEMORY;
-            }
-            if (NP > 0) {
-                memcpy(set+1, parentBag, NP*sizeof(bag_entry));
-                set->numAttrs = NP;
-                TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
-            } else {
-                TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
-                set->numAttrs = 0;
-            }
-            set->availAttrs = NT;
-            set->typeSpecFlags = parentTypeSpecFlags;
+        if (NP > 0) {
+            memcpy(set+1, parentBag, NP*sizeof(bag_entry));
+            set->numAttrs = NP;
+            TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
         } else {
-            set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
-            if (set == NULL) {
-                return NO_MEMORY;
-            }
+            TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
             set->numAttrs = 0;
-            set->availAttrs = N;
-            set->typeSpecFlags = 0;
         }
-
-        if (typeClass->typeSpecFlags != NULL) {
-            set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
-        } else {
-            set->typeSpecFlags = -1;
+        set->availAttrs = NT;
+        set->typeSpecFlags = parentTypeSpecFlags;
+    } else {
+        set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
+        if (set == NULL) {
+            return NO_MEMORY;
         }
+        set->numAttrs = 0;
+        set->availAttrs = N;
+        set->typeSpecFlags = 0;
+    }
 
-        // Now merge in the new attributes...
-        ssize_t curOff = offset;
-        const ResTable_map* map;
-        bag_entry* entries = (bag_entry*)(set+1);
-        size_t curEntry = 0;
-        uint32_t pos = 0;
-        TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
-                     set, entries, set->availAttrs));
-        while (pos < count) {
-            TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
+    set->typeSpecFlags |= entry.specFlags;
 
-            if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) {
-                ALOGW("ResTable_map at %d is beyond type chunk data %d",
-                     (int)curOff, dtohl(type->header.size));
-                return BAD_TYPE;
-            }
-            map = (const ResTable_map*)(((const uint8_t*)type) + curOff);
-            N++;
+    // Now merge in the new attributes...
+    size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
+        + dtohs(entry.entry->size);
+    const ResTable_map* map;
+    bag_entry* entries = (bag_entry*)(set+1);
+    size_t curEntry = 0;
+    uint32_t pos = 0;
+    TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
+                 set, entries, set->availAttrs));
+    while (pos < count) {
+        TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
 
-            const uint32_t newName = htodl(map->name.ident);
-            bool isInside;
-            uint32_t oldName = 0;
-            while ((isInside=(curEntry < set->numAttrs))
-                    && (oldName=entries[curEntry].map.name.ident) < newName) {
-                TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
-                             curEntry, entries[curEntry].map.name.ident));
-                curEntry++;
-            }
+        if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) {
+            ALOGW("ResTable_map at %d is beyond type chunk data %d",
+                 (int)curOff, dtohl(entry.type->header.size));
+            return BAD_TYPE;
+        }
+        map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff);
+        N++;
 
-            if ((!isInside) || oldName != newName) {
-                // This is a new attribute...  figure out what to do with it.
-                if (set->numAttrs >= set->availAttrs) {
-                    // Need to alloc more memory...
-                    const size_t newAvail = set->availAttrs+N;
-                    set = (bag_set*)realloc(set,
-                                            sizeof(bag_set)
-                                            + sizeof(bag_entry)*newAvail);
-                    if (set == NULL) {
-                        return NO_MEMORY;
-                    }
-                    set->availAttrs = newAvail;
-                    entries = (bag_entry*)(set+1);
-                    TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
-                                 set, entries, set->availAttrs));
-                }
-                if (isInside) {
-                    // Going in the middle, need to make space.
-                    memmove(entries+curEntry+1, entries+curEntry,
-                            sizeof(bag_entry)*(set->numAttrs-curEntry));
-                    set->numAttrs++;
-                }
-                TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
-                             curEntry, newName));
-            } else {
-                TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
-                             curEntry, oldName));
-            }
-
-            bag_entry* cur = entries+curEntry;
-
-            cur->stringBlock = package->header->index;
-            cur->map.name.ident = newName;
-            cur->map.value.copyFrom_dtoh(map->value);
-            status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value);
-            if (err != NO_ERROR) {
-                ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data);
-                return UNKNOWN_ERROR;
-            }
-
-            TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
-                         curEntry, cur, cur->stringBlock, cur->map.name.ident,
-                         cur->map.value.dataType, cur->map.value.data));
-
-            // On to the next!
+        const uint32_t newName = htodl(map->name.ident);
+        bool isInside;
+        uint32_t oldName = 0;
+        while ((isInside=(curEntry < set->numAttrs))
+                && (oldName=entries[curEntry].map.name.ident) < newName) {
+            TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
+                         curEntry, entries[curEntry].map.name.ident));
             curEntry++;
-            pos++;
-            const size_t size = dtohs(map->value.size);
-            curOff += size + sizeof(*map)-sizeof(map->value);
-        };
-        if (curEntry > set->numAttrs) {
-            set->numAttrs = curEntry;
         }
+
+        if ((!isInside) || oldName != newName) {
+            // This is a new attribute...  figure out what to do with it.
+            if (set->numAttrs >= set->availAttrs) {
+                // Need to alloc more memory...
+                const size_t newAvail = set->availAttrs+N;
+                set = (bag_set*)realloc(set,
+                                        sizeof(bag_set)
+                                        + sizeof(bag_entry)*newAvail);
+                if (set == NULL) {
+                    return NO_MEMORY;
+                }
+                set->availAttrs = newAvail;
+                entries = (bag_entry*)(set+1);
+                TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
+                             set, entries, set->availAttrs));
+            }
+            if (isInside) {
+                // Going in the middle, need to make space.
+                memmove(entries+curEntry+1, entries+curEntry,
+                        sizeof(bag_entry)*(set->numAttrs-curEntry));
+                set->numAttrs++;
+            }
+            TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
+                         curEntry, newName));
+        } else {
+            TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
+                         curEntry, oldName));
+        }
+
+        bag_entry* cur = entries+curEntry;
+
+        cur->stringBlock = entry.package->header->index;
+        cur->map.name.ident = newName;
+        cur->map.value.copyFrom_dtoh(map->value);
+        status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value);
+        if (err != NO_ERROR) {
+            ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data);
+            return UNKNOWN_ERROR;
+        }
+
+        TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
+                     curEntry, cur, cur->stringBlock, cur->map.name.ident,
+                     cur->map.value.dataType, cur->map.value.data));
+
+        // On to the next!
+        curEntry++;
+        pos++;
+        const size_t size = dtohs(map->value.size);
+        curOff += size + sizeof(*map)-sizeof(map->value);
+    };
+
+    if (curEntry > set->numAttrs) {
+        set->numAttrs = curEntry;
     }
 
     // And this is it...
@@ -4154,80 +4128,63 @@
             continue;
         }
 
-        const ssize_t ti = group->basePackage->typeStrings.indexOfString(type, typeLen);
+        const ssize_t ti = group->findType16(type, typeLen);
         if (ti < 0) {
             TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string()));
             continue;
         }
 
-        const ssize_t ei = group->basePackage->keyStrings.indexOfString(name, nameLen);
-        if (ei < 0) {
-            TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string()));
+        const TypeList& typeList = group->types[ti];
+        if (typeList.isEmpty()) {
+            TABLE_NOISY(printf("Expected type structure not found in package %s for index %d\n",
+                               String8(group->name).string(), ti));
             continue;
         }
 
-        TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei));
+        const size_t typeCount = typeList.size();
+        for (size_t i = 0; i < typeCount; i++) {
+            const Type* t = typeList[i];
+            const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
+            if (ei < 0) {
+                continue;
+            }
 
-        const Type* const typeConfigs = group->packages[0]->getType(ti);
-        if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) {
-            TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n",
-                               String8(group->name).string(), ti));
-        }
-
-        size_t NTC = typeConfigs->configs.size();
-        for (size_t tci=0; tci<NTC; tci++) {
-            const ResTable_type* const ty = typeConfigs->configs[tci];
-            const uint32_t typeOffset = dtohl(ty->entriesStart);
-
-            const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size);
-            const uint32_t* const eindex = (const uint32_t*)
-                (((const uint8_t*)ty) + dtohs(ty->header.headerSize));
-
-            const size_t NE = dtohl(ty->entryCount);
-            for (size_t i=0; i<NE; i++) {
-                uint32_t offset = dtohl(eindex[i]);
-                if (offset == ResTable_type::NO_ENTRY) {
-                    continue;
-                }
-
-                offset += typeOffset;
-
-                if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) {
-                    ALOGW("ResTable_entry at %d is beyond type chunk data %d",
-                         offset, dtohl(ty->header.size));
-                    return 0;
-                }
-                if ((offset&0x3) != 0) {
-                    ALOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s",
-                         (int)offset, (int)group->id, (int)ti+1, (int)i,
-                         String8(package, packageLen).string(),
-                         String8(type, typeLen).string(),
-                         String8(name, nameLen).string());
-                    return 0;
-                }
-
-                const ResTable_entry* const entry = (const ResTable_entry*)
-                    (((const uint8_t*)ty) + offset);
-                if (dtohs(entry->size) < sizeof(*entry)) {
-                    ALOGW("ResTable_entry size %d is too small", dtohs(entry->size));
-                    return BAD_TYPE;
-                }
-
-                TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n",
-                                         i, ei, dtohl(entry->key.index)));
-                if (dtohl(entry->key.index) == (size_t)ei) {
-                    if (outTypeSpecFlags) {
-                        *outTypeSpecFlags = typeConfigs->typeSpecFlags[i];
-                        if (fakePublic) {
-                            *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
-                        }
+            const size_t configCount = t->configs.size();
+            for (size_t j = 0; j < configCount; j++) {
+                const TypeVariant tv(t->configs[j]);
+                for (TypeVariant::iterator iter = tv.beginEntries();
+                     iter != tv.endEntries();
+                     iter++) {
+                    const ResTable_entry* entry = *iter;
+                    if (entry == NULL) {
+                        continue;
                     }
-                    return Res_MAKEID(group->id-1, ti, i);
+
+                    if (dtohl(entry->key.index) == (size_t) ei) {
+                        uint32_t resId = Res_MAKEID(group->id - 1, ti, iter.index());
+                        if (outTypeSpecFlags) {
+                            Entry result;
+                            if (getEntry(group, ti, iter.index(), NULL, &result) != NO_ERROR) {
+                                ALOGW("Failed to find spec flags for %s:%s/%s (0x%08x)",
+                                        String8(group->name).string(),
+                                        String8(String16(type, typeLen)).string(),
+                                        String8(String16(name, nameLen)).string(),
+                                        resId);
+                                return 0;
+                            }
+                            *outTypeSpecFlags = result.specFlags;
+
+                            if (fakePublic) {
+                                *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
+                            }
+                        }
+                        return resId;
+                    }
                 }
             }
         }
+        break;
     }
-
     return 0;
 }
 
@@ -5260,6 +5217,18 @@
     return mPackageGroups[idx]->id;
 }
 
+uint32_t ResTable::getLastTypeIdForPackage(size_t idx) const
+{
+    if (mError != NO_ERROR) {
+        return 0;
+    }
+    LOG_FATAL_IF(idx >= mPackageGroups.size(),
+            "Requested package index %d past package count %d",
+            (int)idx, (int)mPackageGroups.size());
+    const PackageGroup* const group = mPackageGroups[idx];
+    return group->largestTypeId;
+}
+
 size_t ResTable::getTableCount() const
 {
     return mHeaders.size();
@@ -5292,32 +5261,31 @@
 
 void ResTable::getConfigurations(Vector<ResTable_config>* configs) const
 {
-    const size_t I = mPackageGroups.size();
-    for (size_t i=0; i<I; i++) {
+    const size_t packageCount = mPackageGroups.size();
+    for (size_t i = 0; i < packageCount; i++) {
         const PackageGroup* packageGroup = mPackageGroups[i];
-        const size_t J = packageGroup->packages.size();
-        for (size_t j=0; j<J; j++) {
-            const Package* package = packageGroup->packages[j];
-            const size_t K = package->types.size();
-            for (size_t k=0; k<K; k++) {
-                const Type* type = package->types[k];
-                if (type == NULL) continue;
-                const size_t L = type->configs.size();
-                for (size_t l=0; l<L; l++) {
-                    const ResTable_type* config = type->configs[l];
+        const size_t typeCount = packageGroup->types.size();
+        for (size_t j = 0; j < typeCount; j++) {
+            const TypeList& typeList = packageGroup->types[j];
+            const size_t numTypes = typeList.size();
+            for (size_t k = 0; k < numTypes; k++) {
+                const Type* type = typeList[k];
+                const size_t numConfigs = type->configs.size();
+                for (size_t m = 0; m < numConfigs; m++) {
+                    const ResTable_type* config = type->configs[m];
                     ResTable_config cfg;
                     memset(&cfg, 0, sizeof(ResTable_config));
                     cfg.copyFromDtoH(config->config);
                     // only insert unique
-                    const size_t M = configs->size();
-                    size_t m;
-                    for (m=0; m<M; m++) {
-                        if (0 == (*configs)[m].compare(cfg)) {
+                    const size_t N = configs->size();
+                    size_t n;
+                    for (n = 0; n < N; n++) {
+                        if (0 == (*configs)[n].compare(cfg)) {
                             break;
                         }
                     }
                     // if we didn't find it
-                    if (m == M) {
+                    if (n == N) {
                         configs->add(cfg);
                     }
                 }
@@ -5350,122 +5318,180 @@
     }
 }
 
-ssize_t ResTable::getEntry(
-    const Package* package, int typeIndex, int entryIndex,
-    const ResTable_config* config,
-    const ResTable_type** outType, const ResTable_entry** outEntry,
-    const Type** outTypeClass) const
-{
-    ALOGV("Getting entry from package %p\n", package);
-    const ResTable_package* const pkg = package->package;
+StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index)
+    : mPool(pool), mIndex(index) {}
 
-    const Type* allTypes = package->getType(typeIndex);
-    ALOGV("allTypes=%p\n", allTypes);
-    if (allTypes == NULL) {
-        ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
-        return 0;
+StringPoolRef::StringPoolRef()
+    : mPool(NULL), mIndex(0) {}
+
+const char* StringPoolRef::string8(size_t* outLen) const {
+    if (mPool != NULL) {
+        return mPool->string8At(mIndex, outLen);
     }
+    if (outLen != NULL) {
+        *outLen = 0;
+    }
+    return NULL;
+}
 
-    if ((size_t)entryIndex >= allTypes->entryCount) {
-        ALOGW("getEntry failing because entryIndex %d is beyond type entryCount %d",
-            entryIndex, (int)allTypes->entryCount);
+const char16_t* StringPoolRef::string16(size_t* outLen) const {
+    if (mPool != NULL) {
+        return mPool->stringAt(mIndex, outLen);
+    }
+    if (outLen != NULL) {
+        *outLen = 0;
+    }
+    return NULL;
+}
+
+status_t ResTable::getEntry(
+        const PackageGroup* packageGroup, int typeIndex, int entryIndex,
+        const ResTable_config* config,
+        Entry* outEntry) const
+{
+    const TypeList& typeList = packageGroup->types[typeIndex];
+    if (typeList.isEmpty()) {
+        ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
         return BAD_TYPE;
     }
 
-    const ResTable_type* type = NULL;
-    uint32_t offset = ResTable_type::NO_ENTRY;
+    const ResTable_type* bestType = NULL;
+    uint32_t bestOffset = ResTable_type::NO_ENTRY;
+    const Package* bestPackage = NULL;
+    uint32_t specFlags = 0;
+    uint8_t actualTypeIndex = typeIndex;
     ResTable_config bestConfig;
-    memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up
+    memset(&bestConfig, 0, sizeof(bestConfig));
 
-    const size_t NT = allTypes->configs.size();
-    for (size_t i=0; i<NT; i++) {
-        const ResTable_type* const thisType = allTypes->configs[i];
-        if (thisType == NULL) continue;
+    // Iterate over the Types of each package.
+    const size_t typeCount = typeList.size();
+    for (size_t i = 0; i < typeCount; i++) {
+        const Type* const typeSpec = typeList[i];
 
-        ResTable_config thisConfig;
-        thisConfig.copyFromDtoH(thisType->config);
+        int realEntryIndex = entryIndex;
+        int realTypeIndex = typeIndex;
+        bool currentTypeIsOverlay = false;
 
-        TABLE_GETENTRY(ALOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n",
-                           entryIndex, typeIndex+1, dtohl(thisType->config.size),
-                           thisConfig.toString().string()));
-
-        // Check to make sure this one is valid for the current parameters.
-        if (config && !thisConfig.match(*config)) {
-            TABLE_GETENTRY(ALOGI("Does not match config!\n"));
-            continue;
-        }
-
-        // Check if there is the desired entry in this type.
-
-        const uint8_t* const end = ((const uint8_t*)thisType)
-            + dtohl(thisType->header.size);
-        const uint32_t* const eindex = (const uint32_t*)
-            (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize));
-
-        uint32_t thisOffset = dtohl(eindex[entryIndex]);
-        if (thisOffset == ResTable_type::NO_ENTRY) {
-            TABLE_GETENTRY(ALOGI("Skipping because it is not defined!\n"));
-            continue;
-        }
-
-        if (type != NULL) {
-            // Check if this one is less specific than the last found.  If so,
-            // we will skip it.  We check starting with things we most care
-            // about to those we least care about.
-            if (!thisConfig.isBetterThan(bestConfig, config)) {
-                TABLE_GETENTRY(ALOGI("This config is worse than last!\n"));
+        // Runtime overlay packages provide a mapping of app resource
+        // ID to package resource ID.
+        if (typeSpec->idmapEntries.hasEntries()) {
+            uint16_t overlayEntryIndex;
+            if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
+                // No such mapping exists
                 continue;
             }
+            realEntryIndex = overlayEntryIndex;
+            realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
+            currentTypeIsOverlay = true;
         }
 
-        type = thisType;
-        offset = thisOffset;
-        bestConfig = thisConfig;
-        TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n"));
-        if (!config) break;
+        if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
+            ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
+                    Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
+                    entryIndex, static_cast<int>(typeSpec->entryCount));
+            // We should normally abort here, but some legacy apps declare
+            // resources in the 'android' package (old bug in AAPT).
+            continue;
+        }
+
+        // Aggregate all the flags for each package that defines this entry.
+        if (typeSpec->typeSpecFlags != NULL) {
+            specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
+        } else {
+            specFlags = -1;
+        }
+
+        const size_t numConfigs = typeSpec->configs.size();
+        for (size_t c = 0; c < numConfigs; c++) {
+            const ResTable_type* const thisType = typeSpec->configs[c];
+            if (thisType == NULL) {
+                continue;
+            }
+
+            ResTable_config thisConfig;
+            thisConfig.copyFromDtoH(thisType->config);
+
+            // Check to make sure this one is valid for the current parameters.
+            if (config != NULL && !thisConfig.match(*config)) {
+                continue;
+            }
+
+            // Check if there is the desired entry in this type.
+            const uint8_t* const end = reinterpret_cast<const uint8_t*>(thisType)
+                    + dtohl(thisType->header.size);
+            const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
+                    reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
+
+            uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
+            if (thisOffset == ResTable_type::NO_ENTRY) {
+                // There is no entry for this index and configuration.
+                continue;
+            }
+
+            if (bestType != NULL) {
+                // Check if this one is less specific than the last found.  If so,
+                // we will skip it.  We check starting with things we most care
+                // about to those we least care about.
+                if (!thisConfig.isBetterThan(bestConfig, config)) {
+                    if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
+                        continue;
+                    }
+                }
+            }
+
+            bestType = thisType;
+            bestOffset = thisOffset;
+            bestConfig = thisConfig;
+            bestPackage = typeSpec->package;
+            actualTypeIndex = realTypeIndex;
+
+            // If no config was specified, any type will do, so skip
+            if (config == NULL) {
+                break;
+            }
+        }
     }
 
-    if (type == NULL) {
-        TABLE_GETENTRY(ALOGI("No value found for requested entry!\n"));
+    if (bestType == NULL) {
         return BAD_INDEX;
     }
 
-    offset += dtohl(type->entriesStart);
-    TABLE_NOISY(ALOGD("Looking in resource table %p, typeOff=%p, offset=%p",
-            package->header->header, (void*)(((const char*)type)-((const char*)package->header->header)),
-            (void*)offset));
+    bestOffset += dtohl(bestType->entriesStart);
 
-    if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) {
+    if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) {
         ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
-             offset, dtohl(type->header.size));
+                bestOffset, dtohl(bestType->header.size));
         return BAD_TYPE;
     }
-    if ((offset&0x3) != 0) {
-        ALOGW("ResTable_entry at 0x%x is not on an integer boundary",
-             offset);
+    if ((bestOffset & 0x3) != 0) {
+        ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset);
         return BAD_TYPE;
     }
 
-    const ResTable_entry* const entry = (const ResTable_entry*)
-        (((const uint8_t*)type) + offset);
+    const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
+            reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
     if (dtohs(entry->size) < sizeof(*entry)) {
         ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
         return BAD_TYPE;
     }
 
-    *outType = type;
-    *outEntry = entry;
-    if (outTypeClass != NULL) {
-        *outTypeClass = allTypes;
+    if (outEntry != NULL) {
+        outEntry->entry = entry;
+        outEntry->config = bestConfig;
+        outEntry->type = bestType;
+        outEntry->specFlags = specFlags;
+        outEntry->package = bestPackage;
+        outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
+        outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
     }
-    return offset + dtohs(entry->size);
+    return NO_ERROR;
 }
 
 status_t ResTable::parsePackage(const ResTable_package* const pkg,
-                                const Header* const header, uint32_t idmap_id)
+                                const Header* const header)
 {
     const uint8_t* base = (const uint8_t*)pkg;
-    status_t err = validate_chunk(&pkg->header, sizeof(*pkg),
+    status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
                                   header->dataEnd, "ResTable_package");
     if (err != NO_ERROR) {
         return (mError=err);
@@ -5494,89 +5520,88 @@
         return (mError=BAD_TYPE);
     }
 
-    Package* package = NULL;
-    PackageGroup* group = NULL;
-    uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id);
-    // If at this point id == 0, pkg is an overlay package without a
-    // corresponding idmap. During regular usage, overlay packages are
-    // always loaded alongside their idmaps, but during idmap creation
-    // the package is temporarily loaded by itself.
-    if (id < 256) {
+    uint32_t id = dtohl(pkg->id);
+    KeyedVector<uint8_t, IdmapEntries> idmapEntries;
 
-        package = new Package(this, header, pkg);
-        if (package == NULL) {
+    if (header->resourceIDMap != NULL) {
+        uint8_t targetPackageId = 0;
+        status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
+        if (err != NO_ERROR) {
+            ALOGW("Overlay is broken");
+            return (mError=err);
+        }
+        id = targetPackageId;
+    }
+
+    if (id >= 256) {
+        LOG_ALWAYS_FATAL("Package id out of range");
+        return NO_ERROR;
+    } else if (id == 0) {
+        // This is a library so assign an ID
+        id = mNextPackageId++;
+    }
+
+    PackageGroup* group = NULL;
+    Package* package = new Package(this, header, pkg);
+    if (package == NULL) {
+        return (mError=NO_MEMORY);
+    }
+
+    err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
+                                   header->dataEnd-(base+dtohl(pkg->typeStrings)));
+    if (err != NO_ERROR) {
+        delete group;
+        delete package;
+        return (mError=err);
+    }
+
+    err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
+                                  header->dataEnd-(base+dtohl(pkg->keyStrings)));
+    if (err != NO_ERROR) {
+        delete group;
+        delete package;
+        return (mError=err);
+    }
+
+    size_t idx = mPackageMap[id];
+    if (idx == 0) {
+        idx = mPackageGroups.size() + 1;
+
+        char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
+        strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
+        group = new PackageGroup(this, String16(tmpName), id);
+        if (group == NULL) {
+            delete package;
             return (mError=NO_MEMORY);
         }
 
-        if (idmap_id == 0) {
-            err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
-                                           header->dataEnd-(base+dtohl(pkg->typeStrings)));
-            if (err != NO_ERROR) {
-                delete group;
-                delete package;
-                return (mError=err);
-            }
-
-            err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
-                                          header->dataEnd-(base+dtohl(pkg->keyStrings)));
-            if (err != NO_ERROR) {
-                delete group;
-                delete package;
-                return (mError=err);
-            }
-        }
-
-        if (id == 0) {
-            // This is a library so assign an ID
-            id = mNextPackageId++;
-        }
-
-        size_t idx = mPackageMap[id];
-        if (idx == 0) {
-            idx = mPackageGroups.size()+1;
-
-            char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
-            strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
-            group = new PackageGroup(this, String16(tmpName), id);
-            if (group == NULL) {
-                delete package;
-                return (mError=NO_MEMORY);
-            }
-
-            //printf("Adding new package id %d at index %d\n", id, idx);
-            err = mPackageGroups.add(group);
-            if (err < NO_ERROR) {
-                return (mError=err);
-            }
-            group->basePackage = package;
-
-            mPackageMap[id] = (uint8_t)idx;
-
-            // Find all packages that reference this package
-            size_t N = mPackageGroups.size();
-            for (size_t i = 0; i < N; i++) {
-                mPackageGroups[i]->dynamicRefTable.addMapping(
-                        group->name, static_cast<uint8_t>(group->id));
-            }
-        } else {
-            group = mPackageGroups.itemAt(idx-1);
-            if (group == NULL) {
-                return (mError=UNKNOWN_ERROR);
-            }
-        }
-        err = group->packages.add(package);
+        //printf("Adding new package id %d at index %d\n", id, idx);
+        err = mPackageGroups.add(group);
         if (err < NO_ERROR) {
             return (mError=err);
         }
+
+        mPackageMap[id] = static_cast<uint8_t>(idx);
+
+        // Find all packages that reference this package
+        size_t N = mPackageGroups.size();
+        for (size_t i = 0; i < N; i++) {
+            mPackageGroups[i]->dynamicRefTable.addMapping(
+                    group->name, static_cast<uint8_t>(group->id));
+        }
     } else {
-        LOG_ALWAYS_FATAL("Package id out of range");
-        return NO_ERROR;
+        group = mPackageGroups.itemAt(idx - 1);
+        if (group == NULL) {
+            return (mError=UNKNOWN_ERROR);
+        }
     }
 
+    err = group->packages.add(package);
+    if (err < NO_ERROR) {
+        return (mError=err);
+    }
 
     // Iterate through all chunks.
-    size_t curPackage = 0;
-
     const ResChunk_header* chunk =
         (const ResChunk_header*)(((const uint8_t*)pkg)
                                  + dtohs(pkg->header.headerSize));
@@ -5597,6 +5622,7 @@
             }
 
             const size_t typeSpecSize = dtohl(typeSpec->header.size);
+            const size_t newEntryCount = dtohl(typeSpec->entryCount);
 
             LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n",
                                     (void*)(base-(const uint8_t*)chunk),
@@ -5605,12 +5631,11 @@
                                     (void*)typeSpecSize));
             // look for block overrun or int overflow when multiplying by 4
             if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t))
-                    || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount))
+                    || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*newEntryCount)
                     > typeSpecSize)) {
                 ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.",
-                     (void*)(dtohs(typeSpec->header.headerSize)
-                             +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))),
-                     (void*)typeSpecSize);
+                        (void*)(dtohs(typeSpec->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+                        (void*)typeSpecSize);
                 return (mError=BAD_TYPE);
             }
 
@@ -5619,21 +5644,36 @@
                 return (mError=BAD_TYPE);
             }
 
-            while (package->types.size() < typeSpec->id) {
-                package->types.add(NULL);
+            if (newEntryCount > 0) {
+                uint8_t typeIndex = typeSpec->id - 1;
+                ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
+                if (idmapIndex >= 0) {
+                    typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+                }
+
+                TypeList& typeList = group->types.editItemAt(typeIndex);
+                if (!typeList.isEmpty()) {
+                    const Type* existingType = typeList[0];
+                    if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+                        ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
+                                (int) newEntryCount, (int) existingType->entryCount);
+                        // We should normally abort here, but some legacy apps declare
+                        // resources in the 'android' package (old bug in AAPT).
+                    }
+                }
+
+                Type* t = new Type(header, package, newEntryCount);
+                t->typeSpec = typeSpec;
+                t->typeSpecFlags = (const uint32_t*)(
+                        ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+                if (idmapIndex >= 0) {
+                    t->idmapEntries = idmapEntries[idmapIndex];
+                }
+                typeList.add(t);
+                group->largestTypeId = max(group->largestTypeId, typeSpec->id);
+            } else {
+                ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
             }
-            Type* t = package->types[typeSpec->id-1];
-            if (t == NULL) {
-                t = new Type(header, package, dtohl(typeSpec->entryCount));
-                package->types.editItemAt(typeSpec->id-1) = t;
-            } else if (dtohl(typeSpec->entryCount) != t->entryCount) {
-                ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
-                    (int)dtohl(typeSpec->entryCount), (int)t->entryCount);
-                return (mError=BAD_TYPE);
-            }
-            t->typeSpecFlags = (const uint32_t*)(
-                    ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
-            t->typeSpec = typeSpec;
 
         } else if (ctype == RES_TABLE_TYPE_TYPE) {
             const ResTable_type* type = (const ResTable_type*)(chunk);
@@ -5644,50 +5684,69 @@
             }
 
             const uint32_t typeSize = dtohl(type->header.size);
+            const size_t newEntryCount = dtohl(type->entryCount);
 
             LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n",
                                     (void*)(base-(const uint8_t*)chunk),
                                     dtohs(type->header.type),
                                     dtohs(type->header.headerSize),
                                     (void*)typeSize));
-            if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount))
-                > typeSize) {
+            if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount)
+                    > typeSize) {
                 ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
-                     (void*)(dtohs(type->header.headerSize)
-                             +(sizeof(uint32_t)*dtohl(type->entryCount))),
-                     typeSize);
+                        (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+                        typeSize);
                 return (mError=BAD_TYPE);
             }
-            if (dtohl(type->entryCount) != 0
+
+            if (newEntryCount != 0
                 && dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) {
                 ALOGW("ResTable_type entriesStart at 0x%x extends beyond chunk end 0x%x.",
                      dtohl(type->entriesStart), typeSize);
                 return (mError=BAD_TYPE);
             }
+
             if (type->id == 0) {
                 ALOGW("ResTable_type has an id of 0.");
                 return (mError=BAD_TYPE);
             }
 
-            while (package->types.size() < type->id) {
-                package->types.add(NULL);
-            }
-            Type* t = package->types[type->id-1];
-            if (t == NULL) {
-                t = new Type(header, package, dtohl(type->entryCount));
-                package->types.editItemAt(type->id-1) = t;
-            } else if (dtohl(type->entryCount) != t->entryCount) {
-                ALOGW("ResTable_type entry count inconsistent: given %d, previously %d",
-                    (int)dtohl(type->entryCount), (int)t->entryCount);
-                return (mError=BAD_TYPE);
+            if (newEntryCount > 0) {
+                uint8_t typeIndex = type->id - 1;
+                ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
+                if (idmapIndex >= 0) {
+                    typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+                }
+
+                TypeList& typeList = group->types.editItemAt(typeIndex);
+                if (typeList.isEmpty()) {
+                    ALOGE("No TypeSpec for type %d", type->id);
+                    return (mError=BAD_TYPE);
+                }
+
+                Type* t = typeList.editItemAt(typeList.size() - 1);
+                if (newEntryCount != t->entryCount) {
+                    ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
+                        (int)newEntryCount, (int)t->entryCount);
+                    return (mError=BAD_TYPE);
+                }
+
+                if (t->package != package) {
+                    ALOGE("No TypeSpec for type %d", type->id);
+                    return (mError=BAD_TYPE);
+                }
+
+                t->configs.add(type);
+
+                TABLE_GETENTRY(
+                    ResTable_config thisConfig;
+                    thisConfig.copyFromDtoH(type->config);
+                    ALOGI("Adding config to type %d: %s\n",
+                          type->id, thisConfig.toString().string()));
+            } else {
+                ALOGV("Skipping empty ResTable_type for type %d", type->id);
             }
 
-            TABLE_GETENTRY(
-                ResTable_config thisConfig;
-                thisConfig.copyFromDtoH(type->config);
-                ALOGI("Adding config to type %d: %s\n",
-                      type->id, thisConfig.toString().string()));
-            t->configs.add(type);
         } else if (ctype == RES_TABLE_LIBRARY_TYPE) {
             if (group->dynamicRefTable.entries().size() == 0) {
                 status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk);
@@ -5714,10 +5773,6 @@
             (((const uint8_t*)chunk) + csize);
     }
 
-    if (group->typeCount == 0) {
-        group->typeCount = package->types.size();
-    }
-
     return NO_ERROR;
 }
 
@@ -5818,6 +5873,12 @@
     return NO_ERROR;
 }
 
+struct IdmapTypeMap {
+    ssize_t overlayTypeId;
+    size_t entryOffset;
+    Vector<uint32_t> entryMap;
+};
+
 status_t ResTable::createIdmap(const ResTable& overlay,
         uint32_t targetCrc, uint32_t overlayCrc,
         const char* targetPath, const char* overlayPath,
@@ -5828,41 +5889,46 @@
         ALOGW("idmap: target package has no package groups, cannot create idmap\n");
         return UNKNOWN_ERROR;
     }
+
     if (mPackageGroups[0]->packages.size() == 0) {
         ALOGW("idmap: target package has no packages in its first package group, "
                 "cannot create idmap\n");
         return UNKNOWN_ERROR;
     }
 
-    Vector<Vector<uint32_t> > map;
+    KeyedVector<uint8_t, IdmapTypeMap> map;
+
     // overlaid packages are assumed to contain only one package group
     const PackageGroup* pg = mPackageGroups[0];
-    const Package* pkg = pg->packages[0];
-    size_t typeCount = pkg->types.size();
-    // starting size is header + first item (number of types in map)
-    *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t);
+
+    // starting size is header
+    *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES;
+
+    // target package id and number of types in map
+    *outSize += 2 * sizeof(uint16_t);
+
     // overlay packages are assumed to contain only one package group
     const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
-    const uint32_t pkg_id = pkg->package->id << 24;
 
-    for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
-        ssize_t first = -1;
-        ssize_t last = -1;
-        const Type* typeConfigs = pkg->getType(typeIndex);
-        ssize_t mapIndex = map.add();
-        if (mapIndex < 0) {
-            return NO_MEMORY;
+    for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
+        const TypeList& typeList = pg->types[typeIndex];
+        if (typeList.isEmpty()) {
+            continue;
         }
-        Vector<uint32_t>& vector = map.editItemAt(mapIndex);
+
+        const Type* typeConfigs = typeList[0];
+
+        IdmapTypeMap typeMap;
+        typeMap.overlayTypeId = -1;
+        typeMap.entryOffset = 0;
+
         for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
-            uint32_t resID = pkg_id
-                | (0x00ff0000 & ((typeIndex+1)<<16))
-                | (0x0000ffff & (entryIndex));
+            uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex);
             resource_name resName;
             if (!this->getResourceName(resID, false, &resName)) {
-                ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
-                // add dummy value, or trimming leading/trailing zeroes later will fail
-                vector.push(0);
+                if (typeMap.entryMap.isEmpty()) {
+                    typeMap.entryOffset++;
+                }
                 continue;
             }
 
@@ -5874,49 +5940,55 @@
                                                               overlayType.size(),
                                                               overlayPackage.string(),
                                                               overlayPackage.size());
-            if (overlayResID != 0) {
-                overlayResID = pkg_id | (0x00ffffff & overlayResID);
-                last = Res_GETENTRY(resID);
-                if (first == -1) {
-                    first = Res_GETENTRY(resID);
+            if (overlayResID == 0) {
+                if (typeMap.entryMap.isEmpty()) {
+                    typeMap.entryOffset++;
                 }
+                continue;
             }
-            vector.push(overlayResID);
-#if 0
-            if (overlayResID != 0) {
-                ALOGD("%s/%s 0x%08x -> 0x%08x\n",
-                     String8(String16(resName.type)).string(),
-                     String8(String16(resName.name)).string(),
-                     resID, overlayResID);
+
+            if (typeMap.overlayTypeId == -1) {
+                typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
             }
-#endif
+
+            if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) {
+                ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x"
+                        " but entries should map to resources of type %02x",
+                        resID, overlayResID, typeMap.overlayTypeId);
+                return BAD_TYPE;
+            }
+
+            if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
+                // Resize to accomodate this entry and the 0's in between.
+                if (typeMap.entryMap.resize((entryIndex - typeMap.entryOffset) + 1) < 0) {
+                    return NO_MEMORY;
+                }
+                typeMap.entryMap.editTop() = Res_GETENTRY(overlayResID);
+            } else {
+                typeMap.entryMap.add(Res_GETENTRY(overlayResID));
+            }
         }
 
-        if (first != -1) {
-            // shave off trailing entries which lack overlay values
-            const size_t last_past_one = last + 1;
-            if (last_past_one < vector.size()) {
-                vector.removeItemsAt(last_past_one, vector.size() - last_past_one);
+        if (!typeMap.entryMap.isEmpty()) {
+            if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
+                return NO_MEMORY;
             }
-            // shave off leading entries which lack overlay values
-            vector.removeItemsAt(0, first);
-            // store offset to first overlaid resource ID of this type
-            vector.insertAt((uint32_t)first, 0, 1);
-            // reserve space for number and offset of entries, and the actual entries
-            *outSize += (2 + vector.size()) * sizeof(uint32_t);
-        } else {
-            // no entries of current type defined in overlay package
-            vector.clear();
-            // reserve space for type offset
-            *outSize += 1 * sizeof(uint32_t);
+            *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
         }
     }
 
+    if (map.isEmpty()) {
+        ALOGW("idmap: no resources in overlay package present in base package");
+        return UNKNOWN_ERROR;
+    }
+
     if ((*outData = malloc(*outSize)) == NULL) {
         return NO_MEMORY;
     }
+
     uint32_t* data = (uint32_t*)*outData;
     *data++ = htodl(IDMAP_MAGIC);
+    *data++ = htodl(IDMAP_CURRENT_VERSION);
     *data++ = htodl(targetCrc);
     *data++ = htodl(overlayCrc);
     const char* paths[] = { targetPath, overlayPath };
@@ -5934,44 +6006,30 @@
         data += 256 / sizeof(uint32_t);
     }
     const size_t mapSize = map.size();
-    *data++ = htodl(mapSize);
-    size_t offset = mapSize;
+    uint16_t* typeData = reinterpret_cast<uint16_t*>(data);
+    *typeData++ = htods(pg->id);
+    *typeData++ = htods(mapSize);
     for (size_t i = 0; i < mapSize; ++i) {
-        const Vector<uint32_t>& vector = map.itemAt(i);
-        const size_t N = vector.size();
-        if (N == 0) {
-            *data++ = htodl(0);
-        } else {
-            offset++;
-            *data++ = htodl(offset);
-            offset += N;
+        uint8_t targetTypeId = map.keyAt(i);
+        const IdmapTypeMap& typeMap = map[i];
+        *typeData++ = htods(targetTypeId + 1);
+        *typeData++ = htods(typeMap.overlayTypeId);
+        *typeData++ = htods(typeMap.entryMap.size());
+        *typeData++ = htods(typeMap.entryOffset);
+
+        const size_t entryCount = typeMap.entryMap.size();
+        uint32_t* entries = reinterpret_cast<uint32_t*>(typeData);
+        for (size_t j = 0; j < entryCount; j++) {
+            entries[j] = htodl(typeMap.entryMap[j]);
         }
-    }
-    if (offset == mapSize) {
-        ALOGW("idmap: no resources in overlay package present in base package\n");
-        return UNKNOWN_ERROR;
-    }
-    for (size_t i = 0; i < mapSize; ++i) {
-        const Vector<uint32_t>& vector = map.itemAt(i);
-        const size_t N = vector.size();
-        if (N == 0) {
-            continue;
-        }
-        if (N == 1) { // vector expected to hold (offset) + (N > 0 entries)
-            ALOGW("idmap: type %u supposedly has entries, but no entries found\n", (uint32_t)i);
-            return UNKNOWN_ERROR;
-        }
-        *data++ = htodl(N - 1); // do not count the offset (which is vector's first element)
-        for (size_t j = 0; j < N; ++j) {
-            const uint32_t& overlayResID = vector.itemAt(j);
-            *data++ = htodl(overlayResID);
-        }
+        typeData += entryCount * 2;
     }
 
     return NO_ERROR;
 }
 
 bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
+                            uint32_t* pVersion,
                             uint32_t* pTargetCrc, uint32_t* pOverlayCrc,
                             String8* pTargetPath, String8* pOverlayPath)
 {
@@ -5979,17 +6037,20 @@
     if (!assertIdmapHeader(map, sizeBytes)) {
         return false;
     }
+    if (pVersion) {
+        *pVersion = dtohl(map[1]);
+    }
     if (pTargetCrc) {
-        *pTargetCrc = map[1];
+        *pTargetCrc = dtohl(map[2]);
     }
     if (pOverlayCrc) {
-        *pOverlayCrc = map[2];
+        *pOverlayCrc = dtohl(map[3]);
     }
     if (pTargetPath) {
-        pTargetPath->setTo(reinterpret_cast<const char*>(map + 3));
+        pTargetPath->setTo(reinterpret_cast<const char*>(map + 4));
     }
     if (pOverlayPath) {
-        pOverlayPath->setTo(reinterpret_cast<const char*>(map + 3 + 256 / sizeof(uint32_t)));
+        pOverlayPath->setTo(reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t)));
     }
     return true;
 }
@@ -6138,184 +6199,184 @@
         size_t pkgCount = pg->packages.size();
         for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) {
             const Package* pkg = pg->packages[pkgIndex];
-            size_t typeCount = pkg->types.size();
-            printf("  Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex,
-                    pkg->package->id, String8(String16(pkg->package->name)).string(),
-                    (int)typeCount);
-            for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) {
-                const Type* typeConfigs = pkg->getType(typeIndex);
-                if (typeConfigs == NULL) {
-                    printf("    type %d NULL\n", (int)typeIndex);
+            printf("  Package %d id=%d name=%s\n", (int)pkgIndex,
+                    pkg->package->id, String8(String16(pkg->package->name)).string());
+        }
+
+        for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) {
+            const TypeList& typeList = pg->types[typeIndex];
+            if (typeList.isEmpty()) {
+                //printf("    type %d NULL\n", (int)typeIndex);
+                continue;
+            }
+            const Type* typeConfigs = typeList[0];
+            const size_t NTC = typeConfigs->configs.size();
+            printf("    type %d configCount=%d entryCount=%d\n",
+                   (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
+            if (typeConfigs->typeSpecFlags != NULL) {
+                for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
+                    uint32_t resID = (0xff000000 & ((pg->id)<<24))
+                                | (0x00ff0000 & ((typeIndex+1)<<16))
+                                | (0x0000ffff & (entryIndex));
+                    // Since we are creating resID without actually
+                    // iterating over them, we have no idea which is a
+                    // dynamic reference. We must check.
+                    pg->dynamicRefTable.lookupResourceId(&resID);
+
+                    resource_name resName;
+                    if (this->getResourceName(resID, true, &resName)) {
+                        String8 type8;
+                        String8 name8;
+                        if (resName.type8 != NULL) {
+                            type8 = String8(resName.type8, resName.typeLen);
+                        } else {
+                            type8 = String8(resName.type, resName.typeLen);
+                        }
+                        if (resName.name8 != NULL) {
+                            name8 = String8(resName.name8, resName.nameLen);
+                        } else {
+                            name8 = String8(resName.name, resName.nameLen);
+                        }
+                        printf("      spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
+                            resID,
+                            CHAR16_TO_CSTR(resName.package, resName.packageLen),
+                            type8.string(), name8.string(),
+                            dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+                    } else {
+                        printf("      INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+                    }
+                }
+            }
+            for (size_t configIndex=0; configIndex<NTC; configIndex++) {
+                const ResTable_type* type = typeConfigs->configs[configIndex];
+                if ((((uint64_t)type)&0x3) != 0) {
+                    printf("      NON-INTEGER ResTable_type ADDRESS: %p\n", type);
                     continue;
                 }
-                const size_t NTC = typeConfigs->configs.size();
-                printf("    type %d configCount=%d entryCount=%d\n",
-                       (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
-                if (typeConfigs->typeSpecFlags != NULL) {
-                    for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
-                        uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
-                                    | (0x00ff0000 & ((typeIndex+1)<<16))
-                                    | (0x0000ffff & (entryIndex));
-                        // Since we are creating resID without actually
-                        // iterating over them, we have no idea which is a
-                        // dynamic reference. We must check.
-                        pg->dynamicRefTable.lookupResourceId(&resID);
-
-                        resource_name resName;
-                        if (this->getResourceName(resID, true, &resName)) {
-                            String8 type8;
-                            String8 name8;
-                            if (resName.type8 != NULL) {
-                                type8 = String8(resName.type8, resName.typeLen);
-                            } else {
-                                type8 = String8(resName.type, resName.typeLen);
-                            }
-                            if (resName.name8 != NULL) {
-                                name8 = String8(resName.name8, resName.nameLen);
-                            } else {
-                                name8 = String8(resName.name, resName.nameLen);
-                            }
-                            printf("      spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
-                                resID,
-                                CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                                type8.string(), name8.string(),
-                                dtohl(typeConfigs->typeSpecFlags[entryIndex]));
-                        } else {
-                            printf("      INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
-                        }
-                    }
+                String8 configStr = type->config.toString();
+                printf("      config %s:\n", configStr.size() > 0
+                        ? configStr.string() : "(default)");
+                size_t entryCount = dtohl(type->entryCount);
+                uint32_t entriesStart = dtohl(type->entriesStart);
+                if ((entriesStart&0x3) != 0) {
+                    printf("      NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart);
+                    continue;
                 }
-                for (size_t configIndex=0; configIndex<NTC; configIndex++) {
-                    const ResTable_type* type = typeConfigs->configs[configIndex];
-                    if ((((uint64_t)type)&0x3) != 0) {
-                        printf("      NON-INTEGER ResTable_type ADDRESS: %p\n", type);
+                uint32_t typeSize = dtohl(type->header.size);
+                if ((typeSize&0x3) != 0) {
+                    printf("      NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
+                    continue;
+                }
+                for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
+
+                    const uint8_t* const end = ((const uint8_t*)type)
+                        + dtohl(type->header.size);
+                    const uint32_t* const eindex = (const uint32_t*)
+                        (((const uint8_t*)type) + dtohs(type->header.headerSize));
+
+                    uint32_t thisOffset = dtohl(eindex[entryIndex]);
+                    if (thisOffset == ResTable_type::NO_ENTRY) {
                         continue;
                     }
-                    String8 configStr = type->config.toString();
-                    printf("      config %s:\n", configStr.size() > 0
-                            ? configStr.string() : "(default)");
-                    size_t entryCount = dtohl(type->entryCount);
-                    uint32_t entriesStart = dtohl(type->entriesStart);
-                    if ((entriesStart&0x3) != 0) {
-                        printf("      NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart);
-                        continue;
-                    }
-                    uint32_t typeSize = dtohl(type->header.size);
-                    if ((typeSize&0x3) != 0) {
-                        printf("      NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
-                        continue;
-                    }
-                    for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
 
-                        const uint8_t* const end = ((const uint8_t*)type)
-                            + dtohl(type->header.size);
-                        const uint32_t* const eindex = (const uint32_t*)
-                            (((const uint8_t*)type) + dtohs(type->header.headerSize));
-
-                        uint32_t thisOffset = dtohl(eindex[entryIndex]);
-                        if (thisOffset == ResTable_type::NO_ENTRY) {
-                            continue;
-                        }
-
-                        uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
-                                    | (0x00ff0000 & ((typeIndex+1)<<16))
-                                    | (0x0000ffff & (entryIndex));
-                        pg->dynamicRefTable.lookupResourceId(&resID);
-                        resource_name resName;
-                        if (this->getResourceName(resID, true, &resName)) {
-                            String8 type8;
-                            String8 name8;
-                            if (resName.type8 != NULL) {
-                                type8 = String8(resName.type8, resName.typeLen);
-                            } else {
-                                type8 = String8(resName.type, resName.typeLen);
-                            }
-                            if (resName.name8 != NULL) {
-                                name8 = String8(resName.name8, resName.nameLen);
-                            } else {
-                                name8 = String8(resName.name, resName.nameLen);
-                            }
-                            printf("        resource 0x%08x %s:%s/%s: ", resID,
-                                    CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                                    type8.string(), name8.string());
+                    uint32_t resID = (0xff000000 & ((pg->id)<<24))
+                                | (0x00ff0000 & ((typeIndex+1)<<16))
+                                | (0x0000ffff & (entryIndex));
+                    pg->dynamicRefTable.lookupResourceId(&resID);
+                    resource_name resName;
+                    if (this->getResourceName(resID, true, &resName)) {
+                        String8 type8;
+                        String8 name8;
+                        if (resName.type8 != NULL) {
+                            type8 = String8(resName.type8, resName.typeLen);
                         } else {
-                            printf("        INVALID RESOURCE 0x%08x: ", resID);
+                            type8 = String8(resName.type, resName.typeLen);
                         }
-                        if ((thisOffset&0x3) != 0) {
-                            printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset);
-                            continue;
-                        }
-                        if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
-                            printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n",
-                                   entriesStart, thisOffset, typeSize);
-                            continue;
-                        }
-
-                        const ResTable_entry* ent = (const ResTable_entry*)
-                            (((const uint8_t*)type) + entriesStart + thisOffset);
-                        if (((entriesStart + thisOffset)&0x3) != 0) {
-                            printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n",
-                                 (entriesStart + thisOffset));
-                            continue;
-                        }
-
-                        uintptr_t esize = dtohs(ent->size);
-                        if ((esize&0x3) != 0) {
-                            printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
-                            continue;
-                        }
-                        if ((thisOffset+esize) > typeSize) {
-                            printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n",
-                                   entriesStart, thisOffset, (void *)esize, typeSize);
-                            continue;
-                        }
-
-                        const Res_value* valuePtr = NULL;
-                        const ResTable_map_entry* bagPtr = NULL;
-                        Res_value value;
-                        if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
-                            printf("<bag>");
-                            bagPtr = (const ResTable_map_entry*)ent;
+                        if (resName.name8 != NULL) {
+                            name8 = String8(resName.name8, resName.nameLen);
                         } else {
-                            valuePtr = (const Res_value*)
-                                (((const uint8_t*)ent) + esize);
-                            value.copyFrom_dtoh(*valuePtr);
-                            printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
-                                   (int)value.dataType, (int)value.data,
-                                   (int)value.size, (int)value.res0);
+                            name8 = String8(resName.name, resName.nameLen);
                         }
+                        printf("        resource 0x%08x %s:%s/%s: ", resID,
+                                CHAR16_TO_CSTR(resName.package, resName.packageLen),
+                                type8.string(), name8.string());
+                    } else {
+                        printf("        INVALID RESOURCE 0x%08x: ", resID);
+                    }
+                    if ((thisOffset&0x3) != 0) {
+                        printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset);
+                        continue;
+                    }
+                    if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
+                        printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n",
+                               entriesStart, thisOffset, typeSize);
+                        continue;
+                    }
 
-                        if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
-                            printf(" (PUBLIC)");
-                        }
-                        printf("\n");
+                    const ResTable_entry* ent = (const ResTable_entry*)
+                        (((const uint8_t*)type) + entriesStart + thisOffset);
+                    if (((entriesStart + thisOffset)&0x3) != 0) {
+                        printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n",
+                             (entriesStart + thisOffset));
+                        continue;
+                    }
 
-                        if (inclValues) {
-                            if (valuePtr != NULL) {
-                                printf("          ");
-                                print_value(pkg, value);
-                            } else if (bagPtr != NULL) {
-                                const int N = dtohl(bagPtr->count);
-                                const uint8_t* baseMapPtr = (const uint8_t*)ent;
-                                size_t mapOffset = esize;
-                                const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
-                                const uint32_t parent = dtohl(bagPtr->parent.ident);
-                                uint32_t resolvedParent = parent;
-                                status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent);
-                                if (err != NO_ERROR) {
-                                    resolvedParent = 0;
-                                }
-                                printf("          Parent=0x%08x(Resolved=0x%08x), Count=%d\n",
-                                        parent, resolvedParent, N);
-                                for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
-                                    printf("          #%i (Key=0x%08x): ",
-                                        i, dtohl(mapPtr->name.ident));
-                                    value.copyFrom_dtoh(mapPtr->value);
-                                    print_value(pkg, value);
-                                    const size_t size = dtohs(mapPtr->value.size);
-                                    mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
-                                    mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
-                                }
+                    uintptr_t esize = dtohs(ent->size);
+                    if ((esize&0x3) != 0) {
+                        printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
+                        continue;
+                    }
+                    if ((thisOffset+esize) > typeSize) {
+                        printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n",
+                               entriesStart, thisOffset, (void *)esize, typeSize);
+                        continue;
+                    }
+
+                    const Res_value* valuePtr = NULL;
+                    const ResTable_map_entry* bagPtr = NULL;
+                    Res_value value;
+                    if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+                        printf("<bag>");
+                        bagPtr = (const ResTable_map_entry*)ent;
+                    } else {
+                        valuePtr = (const Res_value*)
+                            (((const uint8_t*)ent) + esize);
+                        value.copyFrom_dtoh(*valuePtr);
+                        printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
+                               (int)value.dataType, (int)value.data,
+                               (int)value.size, (int)value.res0);
+                    }
+
+                    if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+                        printf(" (PUBLIC)");
+                    }
+                    printf("\n");
+
+                    if (inclValues) {
+                        if (valuePtr != NULL) {
+                            printf("          ");
+                            print_value(typeConfigs->package, value);
+                        } else if (bagPtr != NULL) {
+                            const int N = dtohl(bagPtr->count);
+                            const uint8_t* baseMapPtr = (const uint8_t*)ent;
+                            size_t mapOffset = esize;
+                            const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+                            const uint32_t parent = dtohl(bagPtr->parent.ident);
+                            uint32_t resolvedParent = parent;
+                            status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent);
+                            if (err != NO_ERROR) {
+                                resolvedParent = 0;
+                            }
+                            printf("          Parent=0x%08x(Resolved=0x%08x), Count=%d\n",
+                                    parent, resolvedParent, N);
+                            for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
+                                printf("          #%i (Key=0x%08x): ",
+                                    i, dtohl(mapPtr->name.ident));
+                                value.copyFrom_dtoh(mapPtr->value);
+                                print_value(typeConfigs->package, value);
+                                const size_t size = dtohs(mapPtr->value.size);
+                                mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
+                                mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
                             }
                         }
                     }
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
new file mode 100644
index 0000000..06b4040
--- /dev/null
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 <androidfw/TypeWrappers.h>
+
+namespace android {
+
+TypeVariant::iterator& TypeVariant::iterator::operator++() {
+    mIndex++;
+    if (mIndex > dtohl(mTypeVariant->data->entryCount)) {
+        mIndex = dtohl(mTypeVariant->data->entryCount);
+    }
+    return *this;
+}
+
+const ResTable_entry* TypeVariant::iterator::operator*() const {
+    const ResTable_type* type = mTypeVariant->data;
+    const uint32_t entryCount = dtohl(type->entryCount);
+    if (mIndex >= entryCount) {
+        return NULL;
+    }
+
+    const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type)
+            + dtohl(type->header.size);
+    const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
+            reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
+    if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+        ALOGE("Type's entry indices extend beyond its boundaries");
+        return NULL;
+    }
+
+    const uint32_t entryOffset = dtohl(entryIndices[mIndex]);
+    if (entryOffset == ResTable_type::NO_ENTRY) {
+        return NULL;
+    }
+
+    if ((entryOffset & 0x3) != 0) {
+        ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset);
+        return NULL;
+    }
+
+    const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+            reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset);
+    if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
+        ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
+        return NULL;
+    } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+        ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
+        return NULL;
+    } else if (dtohs(entry->size) < sizeof(*entry)) {
+        ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+        return NULL;
+    }
+    return entry;
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 9e9649c..4ff6eec 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -1,33 +1,66 @@
-# Build the unit tests.
+#
+# 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.
+#
+
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
 LOCAL_PATH:= $(call my-dir)
+testFiles := \
+    ByteBucketArray_test.cpp \
+    Idmap_test.cpp \
+    ResourceTypes_test.cpp \
+    ResTable_test.cpp \
+    Split_test.cpp \
+    TypeWrappers_test.cpp \
+    ZipUtils_test.cpp
+
+# ==========================================================
+# Build the host tests: libandroidfw_tests
+# ==========================================================
 include $(CLEAR_VARS)
 
-# Build the unit tests.
-test_src_files := \
-    BackupData_test.cpp \
-    ObbFile_test.cpp \
-    ZipUtils_test.cpp \
-    ResourceTypes_test.cpp
+LOCAL_MODULE := libandroidfw_tests
 
-shared_libraries := \
+LOCAL_SRC_FILES := $(testFiles)
+LOCAL_STATIC_LIBRARIES := \
+    libandroidfw \
+    libutils \
+    libcutils \
+	liblog
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+
+# ==========================================================
+# Build the device tests: libandroidfw_tests
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libandroidfw_tests
+
+LOCAL_SRC_FILES := $(testFiles) \
+    BackupData_test.cpp \
+    ObbFile_test.cpp
+
+LOCAL_SHARED_LIBRARIES := \
     libandroidfw \
     libcutils \
     libutils \
     libui \
     libstlport
 
-static_libraries := \
-    libgtest \
-    libgtest_main
-
-$(foreach file,$(test_src_files), \
-    $(eval include $(CLEAR_VARS)) \
-    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
-    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
-    $(eval LOCAL_SRC_FILES := $(file)) \
-    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
-    $(eval include $(BUILD_NATIVE_TEST)) \
-)
-
-# Build the manual test programs.
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
new file mode 100644
index 0000000..376e79c
--- /dev/null
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 <androidfw/ByteBucketArray.h>
+
+#include <gtest/gtest.h>
+
+using android::ByteBucketArray;
+
+TEST(ByteBucketArrayTest, TestSparseInsertion) {
+    ByteBucketArray<int> bba;
+    ASSERT_TRUE(bba.set(0, 1));
+    ASSERT_TRUE(bba.set(10, 2));
+    ASSERT_TRUE(bba.set(26, 3));
+    ASSERT_TRUE(bba.set(129, 4));
+    ASSERT_TRUE(bba.set(234, 5));
+
+    for (size_t i = 0; i < bba.size(); i++) {
+        switch (i) {
+            case 0: EXPECT_EQ(1, bba[i]); break;
+            case 10: EXPECT_EQ(2, bba[i]); break;
+            case 26: EXPECT_EQ(3, bba[i]); break;
+            case 129: EXPECT_EQ(4, bba[i]); break;
+            case 234: EXPECT_EQ(5, bba[i]); break;
+            default: EXPECT_EQ(0, bba[i]); break;
+        }
+    }
+}
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
new file mode 100644
index 0000000..d829b76
--- /dev/null
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+/**
+ * Include a binary resource table.
+ * This table is an overlay.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/overlay/overlay_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+static const uint32_t attr_attr1            = 0x7f010000;
+static const uint32_t attr_attr2            = 0x7f010001;
+static const uint32_t string_test1          = 0x7f020000;
+static const uint32_t string_test2          = 0x7f020001;
+static const uint32_t integer_number1       = 0x7f030000;
+static const uint32_t integer_number2       = 0x7f030001;
+static const uint32_t style_Theme1          = 0x7f040000;
+static const uint32_t style_Theme2          = 0x7f040001;
+static const uint32_t array_integerArray1   = 0x7f050000;
+
+class IdmapTest : public ::testing::Test {
+protected:
+    virtual void SetUp() {
+        ASSERT_EQ(NO_ERROR, mTargetTable.add(basic_arsc, basic_arsc_len));
+        ASSERT_EQ(NO_ERROR, mOverlayTable.add(overlay_arsc, overlay_arsc_len));
+        char targetName[256] = "com.android.test.basic";
+        ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0,
+                    targetName, targetName, &mData, &mDataSize));
+    }
+
+    virtual void TearDown() {
+        free(mData);
+    }
+
+    ResTable mTargetTable;
+    ResTable mOverlayTable;
+    void* mData;
+    size_t mDataSize;
+};
+
+TEST_F(IdmapTest, canLoadIdmap) {
+    ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+}
+
+TEST_F(IdmapTest, overlayOverridesResourceValue) {
+    Res_value val;
+    ssize_t block = mTargetTable.getResource(string_test2, &val, false);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+    const ResStringPool* pool = mTargetTable.getTableStringBlock(block);
+    ASSERT_TRUE(pool != NULL);
+    ASSERT_LT(val.data, pool->size());
+
+    size_t strLen;
+    const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
+    ASSERT_TRUE(targetStr16 != NULL);
+    ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+
+    ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+    ssize_t newBlock = mTargetTable.getResource(string_test2, &val, false);
+    ASSERT_GE(newBlock, 0);
+    ASSERT_NE(block, newBlock);
+    ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+    pool = mTargetTable.getTableStringBlock(newBlock);
+    ASSERT_TRUE(pool != NULL);
+    ASSERT_LT(val.data, pool->size());
+
+    targetStr16 = pool->stringAt(val.data, &strLen);
+    ASSERT_TRUE(targetStr16 != NULL);
+    ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen));
+}
+
+TEST_F(IdmapTest, overlaidResourceHasSameName) {
+    ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+    ResTable::resource_name resName;
+    ASSERT_TRUE(mTargetTable.getResourceName(array_integerArray1, false, &resName));
+
+    ASSERT_TRUE(resName.package != NULL);
+    ASSERT_TRUE(resName.type != NULL);
+    ASSERT_TRUE(resName.name != NULL);
+
+    EXPECT_EQ(String16("com.android.test.basic"), String16(resName.package, resName.packageLen));
+    EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen));
+    EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen));
+}
+
+} // namespace
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
new file mode 100644
index 0000000..54d42c3
--- /dev/null
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -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.
+ */
+
+#include <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+static const uint32_t attr_attr1            = 0x7f010000;
+static const uint32_t attr_attr2            = 0x7f010001;
+static const uint32_t string_test1          = 0x7f020000;
+static const uint32_t string_test2          = 0x7f020001;
+static const uint32_t integer_number1       = 0x7f030000;
+static const uint32_t integer_number2       = 0x7f030001;
+static const uint32_t style_Theme1          = 0x7f040000;
+static const uint32_t style_Theme2          = 0x7f040001;
+static const uint32_t array_integerArray1   = 0x7f050000;
+
+TEST(ResTableTest, shouldLoadSuccessfully) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+}
+
+TEST(ResTableTest, simpleTypeIsRetrievedCorrectly) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    Res_value val;
+    ssize_t block = table.getResource(string_test1, &val, MAY_NOT_BE_BAG);
+
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+
+    const ResStringPool* pool = table.getTableStringBlock(block);
+    ASSERT_TRUE(NULL != pool);
+    ASSERT_EQ(String8("test1"), pool->string8ObjectAt(val.data));
+}
+
+TEST(ResTableTest, resourceNameIsResolved) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    String16 defPackage("com.android.test.basic");
+    String16 testName("@string/test1");
+    uint32_t resID = table.identifierForName(testName.string(), testName.size(),
+                                             0, 0,
+                                             defPackage.string(), defPackage.size());
+    ASSERT_NE(uint32_t(0x00000000), resID);
+    ASSERT_EQ(string_test1, resID);
+}
+
+TEST(ResTableTest, noParentThemeIsAppliedCorrectly) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    ResTable::Theme theme(table);
+    ASSERT_EQ(NO_ERROR, theme.applyStyle(style_Theme1));
+
+    Res_value val;
+    uint32_t specFlags = 0;
+    ssize_t index = theme.getAttribute(attr_attr1, &val, &specFlags);
+    ASSERT_GE(index, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(100), val.data);
+
+    index = theme.getAttribute(attr_attr2, &val, &specFlags);
+    ASSERT_GE(index, 0);
+    ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+    ASSERT_EQ(integer_number1, val.data);
+}
+
+TEST(ResTableTest, parentThemeIsAppliedCorrectly) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    ResTable::Theme theme(table);
+    ASSERT_EQ(NO_ERROR, theme.applyStyle(style_Theme2));
+
+    Res_value val;
+    uint32_t specFlags = 0;
+    ssize_t index = theme.getAttribute(attr_attr1, &val, &specFlags);
+    ASSERT_GE(index, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(300), val.data);
+
+    index = theme.getAttribute(attr_attr2, &val, &specFlags);
+    ASSERT_GE(index, 0);
+    ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+    ASSERT_EQ(integer_number1, val.data);
+}
+
+TEST(ResTableTest, referenceToBagIsNotResolved) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    Res_value val;
+    ssize_t block = table.getResource(integer_number2, &val, MAY_NOT_BE_BAG);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+    ASSERT_EQ(array_integerArray1, val.data);
+
+    ssize_t newBlock = table.resolveReference(&val, block);
+    EXPECT_EQ(block, newBlock);
+    EXPECT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+    EXPECT_EQ(array_integerArray1, val.data);
+}
+
+TEST(ResTableTest, resourcesStillAccessibleAfterParameterChange) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    Res_value val;
+    ssize_t block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+
+    const ResTable::bag_entry* entry;
+    ssize_t count = table.lockBag(array_integerArray1, &entry);
+    ASSERT_GE(count, 0);
+    table.unlockBag(entry);
+
+    ResTable_config param;
+    memset(&param, 0, sizeof(param));
+    param.density = 320;
+    table.setParameters(&param);
+
+    block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+
+    count = table.lockBag(array_integerArray1, &entry);
+    ASSERT_GE(count, 0);
+    table.unlockBag(entry);
+}
+
+TEST(ResTableTest, resourceIsOverridenWithBetterConfig) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+    Res_value val;
+    ssize_t block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(200), val.data);
+
+    ResTable_config param;
+    memset(&param, 0, sizeof(param));
+    param.language[0] = 's';
+    param.language[1] = 'v';
+    param.country[0] = 'S';
+    param.country[1] = 'E';
+    table.setParameters(&param);
+
+    block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG);
+    ASSERT_GE(block, 0);
+    ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+    ASSERT_EQ(uint32_t(400), val.data);
+}
+
+}
diff --git a/libs/androidfw/tests/ResourceTypes_test.cpp b/libs/androidfw/tests/ResourceTypes_test.cpp
index 4888b4a..6041e08 100644
--- a/libs/androidfw/tests/ResourceTypes_test.cpp
+++ b/libs/androidfw/tests/ResourceTypes_test.cpp
@@ -64,8 +64,8 @@
      config.packLanguage("eng");
 
      // 1-00110-01 101-00100
-     EXPECT_EQ(0x99, config.language[0]);
-     EXPECT_EQ(0xa4, config.language[1]);
+     EXPECT_EQ('\x99', config.language[0]);
+     EXPECT_EQ('\xA4', config.language[1]);
 
      char out[4] = { 1, 1, 1, 1};
      config.unpackLanguage(out);
diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp
new file mode 100644
index 0000000..dbfdeae
--- /dev/null
+++ b/libs/androidfw/tests/Split_test.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+
+#include <gtest/gtest.h>
+
+/**
+ * Include a binary resource table. This table
+ * is a base table for an APK split.
+ *
+ * Package: com.android.example.split
+ *
+ * layout/main          0x7f020000 {default, fr-sw600dp-v13}
+ *
+ * string/app_title     0x7f030000 {default}
+ * string/test          0x7f030001 {default}
+ * string/boom          0x7f030002 {default}
+ * string/blah          0x7f030003 {default}
+ *
+ * array/lotsofstrings  0x7f040000 {default}
+ * array/numList        0x7f040001 {default}
+ * array/ary            0x7f040002 {default}
+ *
+ */
+#include "data/split_base_arsc.h"
+
+/**
+ * Include a binary resource table. This table
+ * is a configuration split table for an APK split.
+ *
+ * Package: com.android.example.split
+ *
+ * string/app_title     0x7f030000 {fr}
+ * string/test          0x7f030001 {de,fr}
+ * string/blah          0x7f030003 {fr}
+ *
+ * array/lotsofstrings  0x7f040000 {fr}
+ *
+ */
+#include "data/split_de_fr_arsc.h"
+
+
+using namespace android;
+
+enum { MAY_NOT_BE_BAG = false };
+
+void makeConfigFrench(ResTable_config* config) {
+    memset(config, 0, sizeof(*config));
+    config->language[0] = 'f';
+    config->language[1] = 'r';
+}
+
+TEST(SplitTest, TestLoadBase) {
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len));
+}
+
+TEST(SplitTest, TestGetResourceFromBase) {
+    ResTable_config frenchConfig;
+    makeConfigFrench(&frenchConfig);
+
+    ResTable table;
+    table.setParameters(&frenchConfig);
+
+    ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len));
+
+    ResTable_config expectedConfig;
+    memset(&expectedConfig, 0, sizeof(expectedConfig));
+
+    Res_value val;
+    ResTable_config config;
+    ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, NULL, &config);
+
+    // The returned block should tell us which string pool to get the value, if it is a string.
+    EXPECT_GE(block, 0);
+
+    // We expect the default resource to be selected since it is the only resource configuration.
+    EXPECT_EQ(0, expectedConfig.compare(config));
+
+    EXPECT_EQ(Res_value::TYPE_STRING, val.dataType);
+}
+
+TEST(SplitTest, TestGetResourceFromSplit) {
+    ResTable_config expectedConfig;
+    makeConfigFrench(&expectedConfig);
+
+    ResTable table;
+    table.setParameters(&expectedConfig);
+
+    ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len));
+    ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+    Res_value val;
+    ResTable_config config;
+    ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, NULL, &config);
+
+    EXPECT_GE(block, 0);
+
+    EXPECT_EQ(0, expectedConfig.compare(config));
+
+    EXPECT_EQ(Res_value::TYPE_STRING, val.dataType);
+}
+
+TEST(SplitTest, ResourcesFromBaseAndSplitHaveSameNames) {
+    ResTable_config expectedConfig;
+    makeConfigFrench(&expectedConfig);
+
+    ResTable table;
+    table.setParameters(&expectedConfig);
+
+    ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len));
+
+    ResTable::resource_name baseName;
+    EXPECT_TRUE(table.getResourceName(0x7f030003, false, &baseName));
+
+    ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+    ResTable::resource_name frName;
+    EXPECT_TRUE(table.getResourceName(0x7f030003, false, &frName));
+
+    EXPECT_EQ(
+            String16(baseName.package, baseName.packageLen),
+            String16(frName.package, frName.packageLen));
+
+    EXPECT_EQ(
+            String16(baseName.type, baseName.typeLen),
+            String16(frName.type, frName.typeLen));
+
+    EXPECT_EQ(
+            String16(baseName.name, baseName.nameLen),
+            String16(frName.name, frName.nameLen));
+}
+
+TEST(SplitTest, TypeEntrySpecFlagsAreUpdated) {
+    ResTable_config defaultConfig;
+    memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+    ResTable table;
+    ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len));
+
+    Res_value val;
+    uint32_t specFlags = 0;
+    ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, &specFlags, NULL);
+    EXPECT_GE(block, 0);
+
+    EXPECT_EQ(static_cast<uint32_t>(0), specFlags);
+
+    ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+    uint32_t frSpecFlags = 0;
+    block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, &frSpecFlags, NULL);
+    EXPECT_GE(block, 0);
+
+    EXPECT_EQ(ResTable_config::CONFIG_LOCALE, frSpecFlags);
+}
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
new file mode 100644
index 0000000..75a233a
--- /dev/null
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -0,0 +1,17 @@
+#ifndef __TEST_HELPERS_H
+#define __TEST_HELPERS_H
+
+#include <ostream>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) {
+    return out << str.string();
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) {
+    return out << android::String8(str).string();
+}
+
+#endif // __TEST_HELPERS_H
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
new file mode 100644
index 0000000..d69abe5
--- /dev/null
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+void* createTypeData() {
+    ResTable_type t;
+    memset(&t, 0, sizeof(t));
+    t.header.type = RES_TABLE_TYPE_TYPE;
+    t.header.headerSize = sizeof(t);
+    t.id = 1;
+    t.entryCount = 3;
+
+    uint32_t offsets[3];
+    t.entriesStart = t.header.headerSize + sizeof(offsets);
+    t.header.size = t.entriesStart;
+
+    offsets[0] = 0;
+    ResTable_entry e1;
+    memset(&e1, 0, sizeof(e1));
+    e1.size = sizeof(e1);
+    e1.key.index = 0;
+    t.header.size += sizeof(e1);
+
+    Res_value v1;
+    memset(&v1, 0, sizeof(v1));
+    t.header.size += sizeof(v1);
+
+    offsets[1] = ResTable_type::NO_ENTRY;
+
+    offsets[2] = sizeof(e1) + sizeof(v1);
+    ResTable_entry e2;
+    memset(&e2, 0, sizeof(e2));
+    e2.size = sizeof(e2);
+    e2.key.index = 1;
+    t.header.size += sizeof(e2);
+
+    Res_value v2;
+    memset(&v2, 0, sizeof(v2));
+    t.header.size += sizeof(v2);
+
+    uint8_t* data = (uint8_t*)malloc(t.header.size);
+    uint8_t* p = data;
+    memcpy(p, &t, sizeof(t));
+    p += sizeof(t);
+    memcpy(p, offsets, sizeof(offsets));
+    p += sizeof(offsets);
+    memcpy(p, &e1, sizeof(e1));
+    p += sizeof(e1);
+    memcpy(p, &v1, sizeof(v1));
+    p += sizeof(v1);
+    memcpy(p, &e2, sizeof(e2));
+    p += sizeof(e2);
+    memcpy(p, &v2, sizeof(v2));
+    p += sizeof(v2);
+    return data;
+}
+
+TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
+    ResTable_type* data = (ResTable_type*) createTypeData();
+
+    TypeVariant v(data);
+
+    TypeVariant::iterator iter = v.beginEntries();
+    ASSERT_EQ(uint32_t(0), iter.index());
+    ASSERT_TRUE(NULL != *iter);
+    ASSERT_EQ(uint32_t(0), iter->key.index);
+    ASSERT_NE(v.endEntries(), iter);
+
+    iter++;
+
+    ASSERT_EQ(uint32_t(1), iter.index());
+    ASSERT_TRUE(NULL == *iter);
+    ASSERT_NE(v.endEntries(), iter);
+
+    iter++;
+
+    ASSERT_EQ(uint32_t(2), iter.index());
+    ASSERT_TRUE(NULL != *iter);
+    ASSERT_EQ(uint32_t(1), iter->key.index);
+    ASSERT_NE(v.endEntries(), iter);
+
+    iter++;
+
+    ASSERT_EQ(v.endEntries(), iter);
+
+    free(data);
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/data/.gitignore b/libs/androidfw/tests/data/.gitignore
new file mode 100644
index 0000000..c05cfb0
--- /dev/null
+++ b/libs/androidfw/tests/data/.gitignore
@@ -0,0 +1,2 @@
+*.apk
+*.arsc
diff --git a/libs/androidfw/tests/data/basic/AndroidManifest.xml b/libs/androidfw/tests/data/basic/AndroidManifest.xml
new file mode 100644
index 0000000..a56ac18
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.basic">
+    <application>
+    </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h
new file mode 100644
index 0000000..6532076
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/basic_arsc.h
@@ -0,0 +1,131 @@
+unsigned char basic_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0xfc, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+  0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xb0, 0x05, 0x00, 0x00,
+  0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+  0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+  0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+  0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+  0x05, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+  0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+  0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+  0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+  0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00,
+  0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
+  0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+  0xdc, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+  0x2a, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
+  0x5c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+  0x32, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
+  0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00,
+  0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00,
+  0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00,
+  0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00,
+  0x65, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00, 0x06, 0x00, 0x54, 0x00,
+  0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x31, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+  0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00,
+  0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00, 0x72, 0x00,
+  0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x7f,
+  0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x90, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10,
+  0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x01,
+  0x00, 0x00, 0x03, 0x7f, 0x10, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x04, 0x7f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f,
+  0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+  0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02,
+  0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+  0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00
+};
+unsigned int basic_arsc_len = 1532;
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
new file mode 100755
index 0000000..237342c
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc basic.arsc && \
+xxd -i basic.arsc > basic_arsc.h
diff --git a/libs/androidfw/tests/data/basic/res/values-sv/values.xml b/libs/androidfw/tests/data/basic/res/values-sv/values.xml
new file mode 100644
index 0000000..9d52307
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values-sv/values.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <integer name="number1">400</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
new file mode 100644
index 0000000..662eda6
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <attr name="attr1" format="reference|integer" />
+    <attr name="attr2" format="reference|integer" />
+
+    <string name="test1">test1</string>
+    <string name="test2">test2</string>
+
+    <integer name="number1">200</integer>
+    <integer name="number2">@array/integerArray1</integer>
+
+    <style name="Theme1">
+        <item name="com.android.test.basic:attr1">100</item>
+        <item name="com.android.test.basic:attr2">@integer/number1</item>
+    </style>
+
+    <style name="Theme2" parent="@com.android.test.basic:style/Theme1">
+        <item name="com.android.test.basic:attr1">300</item>
+    </style>
+
+    <integer-array name="integerArray1">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </integer-array>
+</resources>
diff --git a/libs/androidfw/tests/data/overlay/AndroidManifest.xml b/libs/androidfw/tests/data/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..a56ac18
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.basic">
+    <application>
+    </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
new file mode 100755
index 0000000..87cf6de
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc overlay.arsc && \
+xxd -i overlay.arsc > overlay_arsc.h
diff --git a/libs/androidfw/tests/data/overlay/overlay_arsc.h b/libs/androidfw/tests/data/overlay/overlay_arsc.h
new file mode 100644
index 0000000..5bd98b2
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/overlay_arsc.h
@@ -0,0 +1,69 @@
+unsigned char overlay_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x74, 0x00,
+  0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x6f, 0x00,
+  0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xc4, 0x02, 0x00, 0x00,
+  0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+  0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+  0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+  0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+  0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00,
+  0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
+  0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00,
+  0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00,
+  0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+  0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+  0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+  0x0b, 0x00, 0x00, 0x00
+};
+unsigned int overlay_arsc_len = 784;
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
new file mode 100644
index 0000000..227e889
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="test2">test2-overlay</string>
+    <integer-array name="integerArray1">
+        <item>10</item>
+        <item>11</item>
+    </integer-array>
+</resources>
diff --git a/libs/androidfw/tests/data/split_base_arsc.h b/libs/androidfw/tests/data/split_base_arsc.h
new file mode 100644
index 0000000..e0321e9
--- /dev/null
+++ b/libs/androidfw/tests/data/split_base_arsc.h
@@ -0,0 +1,221 @@
+unsigned char split_base_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0x30, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x94, 0x01, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+  0x72, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00,
+  0xb4, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00,
+  0xf4, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x2a, 0x01, 0x00, 0x00,
+  0x44, 0x01, 0x00, 0x00, 0x13, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00,
+  0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00,
+  0x74, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
+  0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x22, 0x00,
+  0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00,
+  0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x2d, 0x00, 0x66, 0x00,
+  0x72, 0x00, 0x2d, 0x00, 0x73, 0x00, 0x77, 0x00, 0x36, 0x00, 0x30, 0x00,
+  0x30, 0x00, 0x64, 0x00, 0x70, 0x00, 0x2d, 0x00, 0x76, 0x00, 0x31, 0x00,
+  0x33, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
+  0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x09, 0x00,
+  0x53, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00,
+  0x41, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x48, 0x00,
+  0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00,
+  0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6d, 0x00,
+  0x21, 0x00, 0x00, 0x00, 0x07, 0x00, 0x42, 0x00, 0x6c, 0x00, 0x61, 0x00,
+  0x68, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x0b, 0x00,
+  0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00,
+  0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x20, 0x00,
+  0x62, 0x00, 0x79, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x49, 0x00,
+  0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x41, 0x00,
+  0x4c, 0x00, 0x4c, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x49, 0x00,
+  0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x31, 0x00,
+  0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x20, 0x00, 0x3a, 0x00, 0x29, 0x00,
+  0x00, 0x00, 0x0b, 0x00, 0x49, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x6f, 0x00,
+  0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x3a, 0x00,
+  0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x01,
+  0x90, 0x08, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+  0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
+  0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x78, 0x00,
+  0x61, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x2e, 0x00,
+  0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x1c, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0xd4, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+  0x3a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+  0x66, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00,
+  0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+  0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00,
+  0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x07, 0x00,
+  0x70, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00,
+  0x73, 0x00, 0x00, 0x00, 0x04, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00,
+  0x6c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00,
+  0x6f, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00,
+  0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00,
+  0x64, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00,
+  0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0x30, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+  0x22, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+  0x46, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00,
+  0x80, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00,
+  0xa4, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x5f, 0x00, 0x74, 0x00,
+  0x69, 0x00, 0x74, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x04, 0x00,
+  0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00,
+  0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x00,
+  0x62, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x68, 0x00, 0x00, 0x00, 0x0d, 0x00,
+  0x6c, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00,
+  0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00,
+  0x73, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+  0x4c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x00, 0x00, 0x03, 0x00,
+  0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x00, 0x00, 0x04, 0x00, 0x70, 0x00,
+  0x6c, 0x00, 0x75, 0x00, 0x72, 0x00, 0x00, 0x00, 0x03, 0x00, 0x71, 0x00,
+  0x75, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x67, 0x00, 0x72, 0x00,
+  0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x77, 0x00,
+  0x69, 0x00, 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x00, 0x00, 0x0a, 0x00,
+  0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00,
+  0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00,
+  0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x04, 0x24, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x94, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+  0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+  0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0xc8, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03,
+  0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03,
+  0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+  0x08, 0x00, 0x00, 0x10, 0xd2, 0x04, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x01, 0x01, 0x00, 0x03, 0x7f,
+  0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x7f,
+  0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x05, 0x01, 0x19, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x7c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x0a, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x58, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x12, 0xff, 0xff, 0xff, 0xff,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x58, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1d, 0x00, 0xff, 0x00, 0xff,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x0b, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x01, 0x17, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05,
+  0x01, 0xe6, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+  0x0b, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+  0x7b, 0x00, 0x00, 0x00
+};
+unsigned int split_base_arsc_len = 2608;
diff --git a/libs/androidfw/tests/data/split_de_fr_arsc.h b/libs/androidfw/tests/data/split_de_fr_arsc.h
new file mode 100644
index 0000000..6f6a416
--- /dev/null
+++ b/libs/androidfw/tests/data/split_de_fr_arsc.h
@@ -0,0 +1,118 @@
+unsigned char split_de_fr_arsc[] = {
+  0x02, 0x00, 0x0c, 0x00, 0x64, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x2c, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
+  0x6c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x41, 0x00, 0x63, 0x00, 0x68, 0x00,
+  0x74, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x21, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x41, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x20, 0x00, 0x44, 0x00,
+  0x69, 0x00, 0x76, 0x00, 0x69, 0x00, 0x73, 0x00, 0xe9, 0x00, 0x00, 0x00,
+  0x0f, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6a, 0x00, 0x6f, 0x00,
+  0x75, 0x00, 0x72, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x4d, 0x00, 0x6f, 0x00,
+  0x6e, 0x00, 0x64, 0x00, 0x65, 0x00, 0x21, 0x00, 0x00, 0x00, 0x06, 0x00,
+  0x42, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x68, 0x00, 0x2e, 0x00, 0x2e, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x48, 0x00, 0xe9, 0x00, 0x20, 0x00, 0x6c, 0x00,
+  0xe0, 0x00, 0x00, 0x00, 0x09, 0x00, 0x41, 0x00, 0x75, 0x00, 0x20, 0x00,
+  0x72, 0x00, 0x65, 0x00, 0x76, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x72, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x01, 0xa0, 0x04, 0x00, 0x00,
+  0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+  0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+  0x64, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x78, 0x00, 0x61, 0x00, 0x6d, 0x00,
+  0x70, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x73, 0x00, 0x70, 0x00,
+  0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x1c, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+  0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+  0x4c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
+  0x74, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+  0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00,
+  0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00,
+  0x67, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
+  0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x07, 0x00, 0x70, 0x00, 0x6c, 0x00,
+  0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00,
+  0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00,
+  0x6e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00,
+  0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+  0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+  0x2e, 0x00, 0x00, 0x00, 0x09, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00,
+  0x5f, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00, 0x65, 0x00,
+  0x00, 0x00, 0x04, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+  0x00, 0x00, 0x04, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x68, 0x00,
+  0x00, 0x00, 0x0d, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x73, 0x00,
+  0x6f, 0x00, 0x66, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00,
+  0x6e, 0x00, 0x67, 0x00, 0x73, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+  0x84, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+  0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x01, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+unsigned int split_de_fr_arsc_len = 1380;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 900a72e..1a96b2f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -13,7 +13,9 @@
 		font/Font.cpp \
 		AmbientShadow.cpp \
 		Animator.cpp \
+		AnimatorManager.cpp \
 		AssetAtlas.cpp \
+		DamageAccumulator.cpp \
 		FontRenderer.cpp \
 		GammaFontRenderer.cpp \
 		Caches.cpp \
@@ -44,6 +46,7 @@
 		RenderBufferCache.cpp \
 		RenderNode.cpp \
 		RenderProperties.cpp \
+		RenderState.cpp \
 		ResourceCache.cpp \
 		ShadowTessellator.cpp \
 		SkiaShader.cpp \
@@ -60,6 +63,7 @@
 	LOCAL_SRC_FILES += \
 		renderthread/CanvasContext.cpp \
 		renderthread/DrawFrameTask.cpp \
+		renderthread/EglManager.cpp \
 		renderthread/RenderProxy.cpp \
 		renderthread/RenderTask.cpp \
 		renderthread/RenderThread.cpp \
@@ -68,12 +72,8 @@
 	intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
 
 	LOCAL_C_INCLUDES += \
-		$(JNI_H_INCLUDE) \
-		$(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 +81,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.cpp b/libs/hwui/Animator.cpp
index eff3011..4a8c122 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -18,6 +18,7 @@
 
 #include "Animator.h"
 
+#include <inttypes.h>
 #include <set>
 
 #include "RenderNode.h"
@@ -35,72 +36,105 @@
         , mDeltaValue(0)
         , mFromValue(0)
         , mInterpolator(0)
-        , mPlayState(NEEDS_START)
+        , mStagingPlayState(NOT_STARTED)
+        , mPlayState(NOT_STARTED)
+        , mHasStartValue(false)
         , mStartTime(0)
-        , mDelayUntil(0)
         , mDuration(300)
         , mStartDelay(0) {
-
 }
 
 BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
-    setInterpolator(NULL);
+    delete mInterpolator;
+}
+
+void BaseRenderNodeAnimator::checkMutable() {
+    // Should be impossible to hit as the Java-side also has guards for this
+    LOG_ALWAYS_FATAL_IF(mStagingPlayState != NOT_STARTED,
+            "Animator has already been started!");
 }
 
 void BaseRenderNodeAnimator::setInterpolator(Interpolator* interpolator) {
+    checkMutable();
     delete mInterpolator;
     mInterpolator = interpolator;
 }
 
 void BaseRenderNodeAnimator::setStartValue(float value) {
-    LOG_ALWAYS_FATAL_IF(mPlayState != NEEDS_START,
-            "Cannot set the start value after the animator has started!");
-    mFromValue = value;
-    mDeltaValue = (mFinalValue - mFromValue);
-    mPlayState = PENDING;
+    checkMutable();
+    doSetStartValue(value);
 }
 
-void BaseRenderNodeAnimator::setupStartValueIfNecessary(RenderNode* target, TreeInfo& info) {
-    if (mPlayState == NEEDS_START) {
-        setStartValue(getValue(target));
-    }
+void BaseRenderNodeAnimator::doSetStartValue(float value) {
+    mFromValue = value;
+    mDeltaValue = (mFinalValue - mFromValue);
+    mHasStartValue = true;
 }
 
 void BaseRenderNodeAnimator::setDuration(nsecs_t duration) {
+    checkMutable();
     mDuration = duration;
 }
 
 void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) {
+    checkMutable();
     mStartDelay = startDelay;
 }
 
-bool BaseRenderNodeAnimator::animate(RenderNode* target, TreeInfo& info) {
-    if (mPlayState == PENDING && mStartDelay > 0 && mDelayUntil == 0) {
-        mDelayUntil = info.frameTimeMs + mStartDelay;
-        return false;
+void BaseRenderNodeAnimator::pushStaging(RenderNode* target, TreeInfo& info) {
+    if (!mHasStartValue) {
+        doSetStartValue(getValue(target));
     }
-
-    if (mDelayUntil > info.frameTimeMs) {
-        return false;
-    }
-
-    if (mPlayState == PENDING) {
-        mPlayState = RUNNING;
-        mStartTime = info.frameTimeMs;
-        // No interpolator was set, use the default
-        if (!mInterpolator) {
-            setInterpolator(Interpolator::createDefaultInterpolator());
+    if (mStagingPlayState > mPlayState) {
+        mPlayState = mStagingPlayState;
+        // Oh boy, we're starting! Man the battle stations!
+        if (mPlayState == RUNNING) {
+            transitionToRunning(info);
         }
     }
+}
+
+void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) {
+    LOG_ALWAYS_FATAL_IF(info.frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", info.frameTimeMs);
+    if (mStartDelay < 0 || mStartDelay > 50000) {
+        ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay);
+    }
+    mStartTime = info.frameTimeMs + mStartDelay;
+    if (mStartTime < 0) {
+        ALOGW("Ended up with a really weird start time of %" PRId64
+                " with frame time %" PRId64 " and start delay %" PRId64,
+                mStartTime, info.frameTimeMs, mStartDelay);
+        // Set to 0 so that the animate() basically instantly finishes
+        mStartTime = 0;
+    }
+    // No interpolator was set, use the default
+    if (!mInterpolator) {
+        setInterpolator(Interpolator::createDefaultInterpolator());
+    }
+    if (mDuration < 0 || mDuration > 50000) {
+        ALOGW("Your duration is strange and confusing: %" PRId64, mDuration);
+    }
+}
+
+bool BaseRenderNodeAnimator::animate(RenderNode* target, TreeInfo& info) {
+    if (mPlayState < RUNNING) {
+        return false;
+    }
+
+    if (mStartTime > info.frameTimeMs) {
+        info.out.hasAnimations |= true;
+        return false;
+    }
 
     float fraction = 1.0f;
-    if (mPlayState == RUNNING) {
-        fraction = mDuration > 0 ? (float)(info.frameTimeMs - mStartTime) / mDuration : 1.0f;
-        if (fraction >= 1.0f) {
-            fraction = 1.0f;
-            mPlayState = FINISHED;
-        }
+    if (mPlayState == RUNNING && mDuration > 0) {
+        fraction = (float)(info.frameTimeMs - mStartTime) / mDuration;
     }
+    if (fraction >= 1.0f) {
+        fraction = 1.0f;
+        mPlayState = FINISHED;
+    }
+
     fraction = mInterpolator->interpolate(fraction);
     setValue(target, mFromValue + (mDeltaValue * fraction));
 
@@ -108,6 +142,8 @@
         callOnFinishedListener(info);
         return true;
     }
+
+    info.out.hasAnimations |= true;
     return false;
 }
 
@@ -153,13 +189,17 @@
 }
 
 void RenderPropertyAnimator::onAttached(RenderNode* target) {
-    if (mPlayState == NEEDS_START
+    if (!mHasStartValue
             && target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
         setStartValue((target->stagingProperties().*mPropertyAccess->getter)());
     }
     (target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
 }
 
+uint32_t RenderPropertyAnimator::dirtyMask() {
+    return mPropertyAccess->dirtyMask;
+}
+
 float RenderPropertyAnimator::getValue(RenderNode* target) const {
     return (target->properties().*mPropertyAccess->getter)();
 }
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index a0c7c55..a981b5a 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -50,17 +50,18 @@
     ANDROID_API void setListener(AnimationListener* listener) {
         mListener = listener;
     }
+    ANDROID_API void start() { mStagingPlayState = RUNNING; }
+    ANDROID_API void cancel() { mStagingPlayState = FINISHED; }
 
-    ANDROID_API virtual void onAttached(RenderNode* target) {}
-
-    // Guaranteed to happen before the staging push
-    void setupStartValueIfNecessary(RenderNode* target, TreeInfo& info);
-
+    virtual void onAttached(RenderNode* target) {}
+    virtual void pushStaging(RenderNode* target, TreeInfo& info);
     bool animate(RenderNode* target, TreeInfo& info);
 
     bool isFinished() { return mPlayState == FINISHED; }
     float finalValue() { return mFinalValue; }
 
+    ANDROID_API virtual uint32_t dirtyMask() { return 0; }
+
 protected:
     BaseRenderNodeAnimator(float finalValue);
     virtual ~BaseRenderNodeAnimator();
@@ -71,8 +72,7 @@
     void callOnFinishedListener(TreeInfo& info);
 
     enum PlayState {
-        NEEDS_START,
-        PENDING,
+        NOT_STARTED,
         RUNNING,
         FINISHED,
     };
@@ -82,13 +82,19 @@
     float mFromValue;
 
     Interpolator* mInterpolator;
+    PlayState mStagingPlayState;
     PlayState mPlayState;
+    bool mHasStartValue;
     nsecs_t mStartTime;
-    nsecs_t mDelayUntil;
     nsecs_t mDuration;
     nsecs_t mStartDelay;
 
     sp<AnimationListener> mListener;
+
+private:
+    void doSetStartValue(float value);
+    inline void checkMutable();
+    void transitionToRunning(TreeInfo& info);
 };
 
 class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -110,14 +116,16 @@
 
     ANDROID_API RenderPropertyAnimator(RenderProperty property, float finalValue);
 
-    ANDROID_API virtual void onAttached(RenderNode* target);
+    virtual void onAttached(RenderNode* target);
+
+    ANDROID_API virtual uint32_t dirtyMask();
 
 protected:
     virtual float getValue(RenderNode* target) const;
     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/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
new file mode 100644
index 0000000..6a10cf8
--- /dev/null
+++ b/libs/hwui/AnimatorManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 "AnimatorManager.h"
+
+#include <algorithm>
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+using namespace std;
+
+static void unref(BaseRenderNodeAnimator* animator) {
+    animator->decStrong(0);
+}
+
+AnimatorManager::AnimatorManager(RenderNode& parent)
+        : mParent(parent) {
+}
+
+AnimatorManager::~AnimatorManager() {
+    for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+    for_each(mAnimators.begin(), mAnimators.end(), unref);
+}
+
+void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+    animator->incStrong(0);
+    animator->onAttached(&mParent);
+    mNewAnimators.push_back(animator.get());
+}
+
+template<typename T>
+static void move_all(T& source, T& dest) {
+    dest.reserve(source.size() + dest.size());
+    for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+        dest.push_back(*it);
+    }
+    source.clear();
+}
+
+void AnimatorManager::pushStaging(TreeInfo& info) {
+    if (mNewAnimators.size()) {
+        // Since this is a straight move, we don't need to inc/dec the ref count
+        move_all(mNewAnimators, mAnimators);
+    }
+    for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+        (*it)->pushStaging(&mParent, info);
+    }
+}
+
+class AnimateFunctor {
+public:
+    AnimateFunctor(RenderNode& target, TreeInfo& info)
+            : mTarget(target), mInfo(info) {}
+
+    bool operator() (BaseRenderNodeAnimator* animator) {
+        bool remove = animator->animate(&mTarget, mInfo);
+        if (remove) {
+            animator->decStrong(0);
+        }
+        return remove;
+    }
+private:
+    RenderNode& mTarget;
+    TreeInfo& mInfo;
+};
+
+void AnimatorManager::animate(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
+
+    mParent.damageSelf(info);
+    info.damageAccumulator->popTransform();
+
+    AnimateFunctor functor(mParent, info);
+    std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    mAnimators.erase(newEnd, mAnimators.end());
+
+    mParent.mProperties.updateMatrix();
+    info.damageAccumulator->pushTransform(&mParent);
+    mParent.damageSelf(info);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
new file mode 100644
index 0000000..2568121
--- /dev/null
+++ b/libs/hwui/AnimatorManager.h
@@ -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.
+ */
+#ifndef ANIMATORMANAGER_H
+#define ANIMATORMANAGER_H
+
+#include <vector>
+
+#include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include "TreeInfo.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class BaseRenderNodeAnimator;
+class RenderNode;
+
+// Responsible for managing the animators for a single RenderNode
+class AnimatorManager {
+    PREVENT_COPY_AND_ASSIGN(AnimatorManager);
+public:
+    AnimatorManager(RenderNode& parent);
+    ~AnimatorManager();
+
+    void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
+    void pushStaging(TreeInfo& info);
+    void animate(TreeInfo& info);
+
+private:
+    RenderNode& mParent;
+
+    // To improve the efficiency of resizing & removing from the vector
+    // use manual ref counting instead of sp<>.
+    std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+    std::vector<BaseRenderNodeAnimator*> mAnimators;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATORMANAGER_H */
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 6fd9999..402f28b 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -565,11 +565,8 @@
     // call, any texture operation will be performed on the default
     // texture (name=0)
 
-    for (int i = 0; i < REQUIRED_TEXTURE_UNITS_COUNT; i++) {
-        if (mBoundTextures[i] == texture) {
-            mBoundTextures[i] = 0;
-        }
-    }
+    unbindTexture(texture);
+
     glDeleteTextures(1, &texture);
 }
 
@@ -577,6 +574,14 @@
     memset(mBoundTextures, 0, REQUIRED_TEXTURE_UNITS_COUNT * sizeof(GLuint));
 }
 
+void Caches::unbindTexture(GLuint texture) {
+    for (int i = 0; i < REQUIRED_TEXTURE_UNITS_COUNT; i++) {
+        if (mBoundTextures[i] == texture) {
+            mBoundTextures[i] = 0;
+        }
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Scissor
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index b4b5927..83a5d9a 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -261,6 +261,11 @@
     void resetBoundTextures();
 
     /**
+     * Clear the cache of bound textures.
+     */
+    void unbindTexture(GLuint texture);
+
+    /**
      * Sets the scissor for the current surface.
      */
     bool setScissor(GLint x, GLint y, GLint width, GLint height);
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
new file mode 100644
index 0000000..8b32c40
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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 {
+    TransformInvalid = 0,
+    TransformRenderNode,
+    TransformMatrix4,
+    TransformNone,
+};
+
+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;
+    mHead->type = TransformNone;
+}
+
+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::pushNullTransform() {
+    pushCommon();
+    mHead->type = TransformNone;
+}
+
+void DamageAccumulator::popTransform() {
+    LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
+    DirtyStack* dirtyFrame = mHead;
+    mHead = mHead->prev;
+    switch (dirtyFrame->type) {
+    case TransformRenderNode:
+        applyRenderNodeTransform(dirtyFrame);
+        break;
+    case TransformMatrix4:
+        applyMatrix4Transform(dirtyFrame);
+        break;
+    case TransformNone:
+        mHead->pendingDirty.join(dirtyFrame->pendingDirty);
+        break;
+    default:
+        LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type);
+    }
+}
+
+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();
+    if (props.getAlpha() <= 0) {
+        return;
+    }
+
+    // Perform clipping
+    if (props.getClipDamageToBounds() && !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);
+        }
+
+        frame->pendingDirty.setEmpty();
+    }
+}
+
+void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
+    mHead->pendingDirty.join(left, top, right, bottom);
+}
+
+void DamageAccumulator::peekAtDirty(SkRect* dest) {
+    *dest = mHead->pendingDirty;
+}
+
+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..fc9b41b
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.h
@@ -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.
+ */
+#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 pushNullTransform() = 0;
+    virtual void popTransform() = 0;
+    virtual void dirty(float left, float top, float right, float bottom) = 0;
+    virtual void peekAtDirty(SkRect* dest) = 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);
+    // This is used in combination with peekAtDirty to inspect the damage
+    // area of a subtree
+    virtual void pushNullTransform();
+
+    // 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);
+
+    // Returns the current dirty area, *NOT* transformed by pushed transforms
+    virtual void peekAtDirty(SkRect* dest);
+
+    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 pushNullTransform() { }
+    virtual void popTransform() { }
+    virtual void dirty(float left, float top, float right, float bottom) { }
+    virtual void peekAtDirty(SkRect* dest) { dest->setEmpty(); }
+
+    ANDROID_API static NullDamageAccumulator* instance();
+
+private:
+    NullDamageAccumulator() {}
+    ~NullDamageAccumulator() {}
+
+    static NullDamageAccumulator sInstance;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DAMAGEACCUMULATOR_H */
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 937bf8d..a998594 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -500,7 +500,7 @@
 void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
     /* 1: op calculates local bounds */
     DeferredDisplayState* const state = createState();
-    if (op->getLocalBounds(renderer.getDrawModifiers(), state->mBounds)) {
+    if (op->getLocalBounds(state->mBounds)) {
         if (state->mBounds.isEmpty()) {
             // valid empty bounds, don't bother deferring
             tryRecycleState(state);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 48489c2..8a015b21 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -38,7 +38,6 @@
 class StateOp;
 
 class DeferredDisplayState;
-class OpenGLRenderer;
 
 class Batch;
 class DrawBatch;
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 97e9bf6..02b0372 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -27,8 +27,7 @@
 }
 
 DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer, LayerDestroyer destroyer)
-        : mDisplayList(0)
-        , mSurfaceTexture(0)
+        : mSurfaceTexture(0)
         , mTransform(0)
         , mNeedsGLContextAttach(false)
         , mUpdateTexImage(false)
@@ -41,7 +40,6 @@
     mColorFilter = SkSafeRef(mLayer->getColorFilter());
     mAlpha = mLayer->getAlpha();
     mMode = mLayer->getMode();
-    mDirtyRect.setEmpty();
 
     if (!mDestroyer) {
         mDestroyer = defaultLayerDestroyer;
@@ -60,33 +58,13 @@
     SkRefCnt_SafeAssign(mColorFilter, colorFilter);
 }
 
-void DeferredLayerUpdater::setDisplayList(RenderNode* displayList,
-        int left, int top, int right, int bottom) {
-    mDisplayList = displayList;
-    if (mDirtyRect.isEmpty()) {
-        mDirtyRect.set(left, top, right, bottom);
-    } else {
-        mDirtyRect.unionWith(Rect(left, top, right, bottom));
-    }
-}
-
-bool DeferredLayerUpdater::apply(TreeInfo& info) {
+bool DeferredLayerUpdater::apply() {
     bool success = true;
     // These properties are applied the same to both layer types
     mLayer->setColorFilter(mColorFilter);
     mLayer->setAlpha(mAlpha, mMode);
 
-    if (mDisplayList.get()) {
-        if (mWidth != mLayer->layer.getWidth() || mHeight != mLayer->layer.getHeight()) {
-            success = LayerRenderer::resizeLayer(mLayer, mWidth, mHeight);
-        }
-        mLayer->setBlend(mBlend);
-        mDisplayList->prepareTree(info);
-        mLayer->updateDeferred(mDisplayList.get(),
-                mDirtyRect.left, mDirtyRect.top, mDirtyRect.right, mDirtyRect.bottom);
-        mDirtyRect.setEmpty();
-        mDisplayList = 0;
-    } else if (mSurfaceTexture.get()) {
+    if (mSurfaceTexture.get()) {
         if (mNeedsGLContextAttach) {
             mNeedsGLContextAttach = false;
             mSurfaceTexture->attachToContext(mLayer->getTexture());
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index b7cfe80..5905b95 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -23,7 +23,6 @@
 #include <utils/StrongPointer.h>
 
 #include "Layer.h"
-#include "OpenGLRenderer.h"
 #include "Rect.h"
 #include "RenderNode.h"
 
@@ -74,12 +73,9 @@
         mTransform = matrix ? new SkMatrix(*matrix) : 0;
     }
 
-    ANDROID_API void setDisplayList(RenderNode* displayList,
-                int left, int top, int right, int bottom);
-
     ANDROID_API void setPaint(const SkPaint* paint);
 
-    ANDROID_API bool apply(TreeInfo& info);
+    ANDROID_API bool apply();
 
     ANDROID_API Layer* backingLayer() {
         return mLayer;
@@ -94,11 +90,6 @@
     int mAlpha;
     SkXfermode::Mode mMode;
 
-    // Layer type specific properties
-    // displayList and surfaceTexture are mutually exclusive, only 1 may be set
-    // dirtyRect is only valid if displayList is set
-    sp<RenderNode> mDisplayList;
-    Rect mDirtyRect;
     sp<GLConsumer> mSurfaceTexture;
     SkMatrix* mTransform;
     bool mNeedsGLContextAttach;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 96c6292..e38b532 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,12 +87,11 @@
     paints.clear();
     regions.clear();
     paths.clear();
-    matrices.clear();
     layers.clear();
 }
 
-void DisplayListData::addChild(DrawDisplayListOp* op) {
-    LOG_ALWAYS_FATAL_IF(!op->renderNode(), "DrawDisplayListOp with no render node!");
+void DisplayListData::addChild(DrawRenderNodeOp* op) {
+    LOG_ALWAYS_FATAL_IF(!op->renderNode(), "DrawRenderNodeOp with no render node!");
 
     mChildren.push(op);
     mReferenceHolders.push(op->renderNode());
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 11e78b0..bfffbb4 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -61,7 +61,7 @@
 class SaveLayerOp;
 class SaveOp;
 class RestoreToCountOp;
-class DrawDisplayListOp;
+class DrawRenderNodeOp;
 
 /**
  * Holds data used in the playback a tree of DisplayLists.
@@ -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;
@@ -134,8 +133,8 @@
         return !displayListOps.size();
     }
 
-    void addChild(DrawDisplayListOp* childOp);
-    const Vector<DrawDisplayListOp*>& children() { return mChildren; }
+    void addChild(DrawRenderNodeOp* childOp);
+    const Vector<DrawRenderNodeOp*>& children() { return mChildren; }
 
     void refProperty(CanvasPropertyPrimitive* prop) {
         mReferenceHolders.push(prop);
@@ -149,7 +148,7 @@
     Vector< sp<VirtualLightRefBase> > mReferenceHolders;
 
     // list of children display lists for quick, non-drawing traversal
-    Vector<DrawDisplayListOp*> mChildren;
+    Vector<DrawRenderNodeOp*> mChildren;
 
     void cleanupResources();
 };
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 3281116..9fb972b 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -171,7 +171,7 @@
      *
      * returns true if bounds exist
      */
-    virtual bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+    virtual bool getLocalBounds(Rect& localBounds) {
         return false;
     }
 
@@ -253,7 +253,7 @@
     // default empty constructor for bounds, to be overridden in child constructor body
     DrawBoundedOp(const SkPaint* paint): DrawOp(paint) { }
 
-    bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+    virtual bool getLocalBounds(Rect& localBounds) {
         localBounds.set(mLocalBounds);
         OpenGLRenderer::TextShadow textShadow;
         if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) {
@@ -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"; }
@@ -1029,7 +1029,7 @@
     DrawStrokableOp(float left, float top, float right, float bottom, const SkPaint* paint)
             : DrawBoundedOp(left, top, right, bottom, paint) {};
 
-    bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+    virtual bool getLocalBounds(Rect& localBounds) {
         localBounds.set(mLocalBounds);
         if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) {
             localBounds.outset(strokeWidthOutset());
@@ -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,15 +1111,15 @@
     }
 
     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,
             const DeferredDisplayState& state) {
         DrawStrokableOp::onDefer(renderer, deferInfo, state);
         if (!mPaint->getPathEffect()) {
-            renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix,
-                    mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy, mPaint);
+            renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix, *mPaint,
+                    mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy);
         }
     }
 
@@ -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"; }
@@ -1399,7 +1399,7 @@
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         Rect bounds;
-        getLocalBounds(renderer.getDrawModifiers(), bounds);
+        getLocalBounds(bounds);
         return renderer.drawText(mText, mBytesCount, mCount, mX, mY,
                 mPositions, getPaint(renderer), mTotalAdvance, bounds);
     }
@@ -1465,48 +1465,48 @@
     Functor* mFunctor;
 };
 
-class DrawDisplayListOp : public DrawBoundedOp {
-    friend class RenderNode; // grant DisplayList access to info of child
+class DrawRenderNodeOp : public DrawBoundedOp {
+    friend class RenderNode; // grant RenderNode access to info of child
 public:
-    DrawDisplayListOp(RenderNode* displayList, int flags, const mat4& transformFromParent)
-            : DrawBoundedOp(0, 0, displayList->getWidth(), displayList->getHeight(), 0),
-            mDisplayList(displayList), mFlags(flags), mTransformFromParent(transformFromParent) {}
+    DrawRenderNodeOp(RenderNode* renderNode, int flags, const mat4& transformFromParent)
+            : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), 0),
+            mRenderNode(renderNode), mFlags(flags), mTransformFromParent(transformFromParent) {}
 
     virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
             bool useQuickReject) {
-        if (mDisplayList && mDisplayList->isRenderable() && !mSkipInOrderDraw) {
-            mDisplayList->deferNodeInParent(deferStruct, level + 1);
+        if (mRenderNode && mRenderNode->isRenderable() && !mSkipInOrderDraw) {
+            mRenderNode->defer(deferStruct, level + 1);
         }
     }
     virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
             bool useQuickReject) {
-        if (mDisplayList && mDisplayList->isRenderable() && !mSkipInOrderDraw) {
-            mDisplayList->replayNodeInParent(replayStruct, level + 1);
+        if (mRenderNode && mRenderNode->isRenderable() && !mSkipInOrderDraw) {
+            mRenderNode->replay(replayStruct, level + 1);
         }
     }
 
-    // NOT USED since replay() is overridden
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
-        return DrawGlInfo::kStatusDone;
+        LOG_ALWAYS_FATAL("should not be called, because replay() is overridden");
+        return 0;
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw Display List %p, flags %#x", mDisplayList, mFlags);
-        if (mDisplayList && (logFlags & kOpLogFlag_Recurse)) {
-            mDisplayList->output(level + 1);
+        OP_LOG("Draw Display List %p, flags %#x", mRenderNode, mFlags);
+        if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
+            mRenderNode->output(level + 1);
         }
     }
 
-    virtual const char* name() { return "DrawDisplayList"; }
+    virtual const char* name() { return "DrawRenderNode"; }
 
-    RenderNode* renderNode() { return mDisplayList; }
+    RenderNode* renderNode() { return mRenderNode; }
 
 private:
-    RenderNode* mDisplayList;
+    RenderNode* mRenderNode;
     const int mFlags;
 
     ///////////////////////////
-    // Properties below are used by DisplayList::computeOrderingImpl() and iterate()
+    // Properties below are used by RenderNode::computeOrderingImpl() and issueOperations()
     ///////////////////////////
     /**
      * Records transform vs parent, used for computing total transform without rerunning DL contents
@@ -1514,12 +1514,12 @@
     const mat4 mTransformFromParent;
 
     /**
-     * Holds the transformation between the projection surface ViewGroup and this DisplayList
+     * Holds the transformation between the projection surface ViewGroup and this RenderNode
      * drawing instance. Represents any translations / transformations done within the drawing of
      * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
      * DisplayList draw instance.
      *
-     * Note: doesn't include any transformation recorded within the DisplayList and its properties.
+     * Note: doesn't include transformation within the RenderNode, or its properties.
      */
     mat4 mTransformFromCompositingAncestor;
     bool mSkipInOrderDraw;
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 229afdf..7789358 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);
 }
@@ -171,18 +169,16 @@
     return StatefulBaseRenderer::clipRegion(region, op);
 }
 
-status_t DisplayListRenderer::drawDisplayList(RenderNode* displayList,
-        Rect& dirty, int32_t flags) {
+status_t DisplayListRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t flags) {
     // dirty is an out parameter and should not be recorded,
     // it matters only when replaying the display list
 
-    if (displayList->stagingProperties().isProjectionReceiver()) {
+    if (renderNode->stagingProperties().isProjectionReceiver()) {
         // use staging property, since recording on UI thread
         mDisplayListData->projectionReceiveIndex = mDisplayListData->displayListOps.size();
     }
 
-    DrawDisplayListOp* op = new (alloc()) DrawDisplayListOp(displayList,
-            flags, *currentTransform());
+    DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(renderNode, flags, *currentTransform());
     addDrawOp(op);
     mDisplayListData->addChild(op);
     return DrawGlInfo::kStatusDone;
@@ -203,10 +199,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));
@@ -311,6 +306,10 @@
 
 status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
+    if (fabs(sweepAngle) > 360.0f) {
+        return drawOval(left, top, right, bottom, paint);
+    }
+
     paint = refPaint(paint);
     addDrawOp(new (alloc()) DrawArcOp(left, top, right, bottom,
                     startAngle, sweepAngle, useCenter, paint));
@@ -426,7 +425,7 @@
 
 void DisplayListRenderer::addDrawOp(DrawOp* op) {
     Rect localBounds;
-    if (op->getLocalBounds(mDrawModifiers, localBounds)) {
+    if (op->getLocalBounds(localBounds)) {
         bool rejected = quickRejectConservative(localBounds.left, localBounds.top,
                 localBounds.right, localBounds.bottom);
         op->setQuickRejected(rejected);
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index dff4f6c..8ca9af1 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -23,7 +23,6 @@
 #include <cutils/compiler.h>
 
 #include "DisplayListLogBuffer.h"
-#include "OpenGLRenderer.h"
 #include "RenderNode.h"
 
 namespace android {
@@ -53,12 +52,12 @@
 /**
  * Records drawing commands in a display list for later playback into an OpenGLRenderer.
  */
-class DisplayListRenderer: public OpenGLRenderer {
+class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
 public:
-    ANDROID_API DisplayListRenderer();
+    DisplayListRenderer();
     virtual ~DisplayListRenderer();
 
-    ANDROID_API DisplayListData* finishRecording();
+    DisplayListData* finishRecording();
 
     virtual bool isRecording() const { return true; }
 
@@ -81,13 +80,13 @@
             const SkPaint* paint, int flags);
 
     // Matrix
-    virtual void translate(float dx, float dy, float dz);
+    virtual void translate(float dx, float dy, float dz = 0.0f);
     virtual void rotate(float degrees);
     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);
@@ -98,6 +97,10 @@
     virtual void resetPaintFilter();
     virtual void setupPaintFilter(int clearBits, int setBits);
 
+    bool isCurrentTransformSimple() {
+        return currentTransform()->isSimple();
+    }
+
 // ----------------------------------------------------------------------------
 // Canvas draw operations
 // ----------------------------------------------------------------------------
@@ -106,7 +109,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,
@@ -148,16 +151,10 @@
 // Canvas draw operations - special
 // ----------------------------------------------------------------------------
     virtual status_t drawLayer(Layer* layer, float x, float y);
-    virtual status_t drawDisplayList(RenderNode* displayList, Rect& dirty,
-            int32_t replayFlags);
+    virtual status_t drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags);
 
     // TODO: rename for consistency
     virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty);
-protected:
-    // NOTE: must override these to avoid calling into super class, which calls GL. These may be
-    // removed once DisplayListRenderer no longer inherits from OpenGLRenderer
-    virtual void onViewportInitialized() {};
-    virtual void onSnapshotRestored() {};
 
 private:
     void insertRestoreToCount();
@@ -238,17 +235,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/Interpolator.cpp b/libs/hwui/Interpolator.cpp
index 1f84b86..fc0e8a0 100644
--- a/libs/hwui/Interpolator.cpp
+++ b/libs/hwui/Interpolator.cpp
@@ -112,6 +112,10 @@
     int i1 = (int) ipart;
     int i2 = MathUtils::min(i1 + 1, mSize - 1);
 
+    LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
+            " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
+            i1, i2, input, lutpos, mSize, mValues, ipart, weight);
+
     float v1 = mValues[i1];
     float v2 = mValues[i2];
 
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 6a2ef2a..73a4da5 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -20,6 +20,7 @@
 
 #include "Caches.h"
 #include "DeferredDisplayList.h"
+#include "RenderState.h"
 #include "Layer.h"
 #include "LayerRenderer.h"
 #include "OpenGLRenderer.h"
@@ -28,8 +29,10 @@
 namespace android {
 namespace uirenderer {
 
-Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight):
-        caches(Caches::getInstance()), texture(caches) {
+Layer::Layer(RenderState& renderState, const uint32_t layerWidth, const uint32_t layerHeight)
+        : caches(Caches::getInstance())
+        , renderState(renderState)
+        , texture(caches) {
     mesh = NULL;
     meshElementCount = 0;
     cacheable = true;
@@ -41,7 +44,7 @@
     colorFilter = NULL;
     deferredUpdateScheduled = false;
     renderer = NULL;
-    displayList = NULL;
+    renderNode = NULL;
     fbo = 0;
     stencil = NULL;
     debugDrawUpdate = false;
@@ -72,7 +75,7 @@
 
 void Layer::requireRenderer() {
     if (!renderer) {
-        renderer = new LayerRenderer(this);
+        renderer = new LayerRenderer(renderState, this);
         renderer->initProperties();
     }
 }
@@ -123,28 +126,26 @@
 
 void Layer::removeFbo(bool flush) {
     if (stencil) {
-        GLuint previousFbo;
-        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
-        if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        GLuint previousFbo = renderState.getFramebuffer();
+        renderState.bindFramebuffer(fbo);
         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
-        if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+        renderState.bindFramebuffer(previousFbo);
 
         caches.renderBufferCache.put(stencil);
         stencil = NULL;
     }
 
     if (fbo) {
-        if (flush) LayerRenderer::flushLayer(this);
+        if (flush) LayerRenderer::flushLayer(renderState, this);
         // If put fails the cache will delete the FBO
         caches.fboCache.put(fbo);
         fbo = 0;
     }
 }
 
-void Layer::updateDeferred(RenderNode* displayList,
-        int left, int top, int right, int bottom) {
+void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom) {
     requireRenderer();
-    this->displayList = displayList;
+    this->renderNode = renderNode;
     const Rect r(left, top, right, bottom);
     dirtyRect.unionWith(r);
     deferredUpdateScheduled = true;
@@ -185,6 +186,7 @@
 }
 
 void Layer::clearTexture() {
+    caches.unbindTexture(texture.id);
     texture.id = 0;
 }
 
@@ -218,14 +220,14 @@
     renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
             dirtyRect.right, dirtyRect.bottom, !isBlend());
 
-    displayList->computeOrdering();
-    displayList->deferNodeTree(deferredState);
+    renderNode->computeOrdering();
+    renderNode->defer(deferredState, 0);
 
     deferredUpdateScheduled = false;
 }
 
 void Layer::cancelDefer() {
-    displayList = NULL;
+    renderNode = NULL;
     deferredUpdateScheduled = false;
     if (deferredList) {
         delete deferredList;
@@ -245,7 +247,7 @@
         renderer->finish();
 
         dirtyRect.setEmpty();
-        displayList = NULL;
+        renderNode = NULL;
     }
 }
 
@@ -254,14 +256,14 @@
     renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
             !isBlend());
 
-    renderer->drawDisplayList(displayList.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren);
+    renderer->drawRenderNode(renderNode.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren);
 
     renderer->finish();
 
     dirtyRect.setEmpty();
 
     deferredUpdateScheduled = false;
-    displayList = NULL;
+    renderNode = NULL;
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 49610d5..0bf05d0 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -43,6 +43,7 @@
 
 // Forward declarations
 class Caches;
+class RenderState;
 class OpenGLRenderer;
 class RenderNode;
 class DeferredDisplayList;
@@ -53,7 +54,7 @@
  */
 class Layer {
 public:
-    Layer(const uint32_t layerWidth, const uint32_t layerHeight);
+    Layer(RenderState& renderState, const uint32_t layerWidth, const uint32_t layerHeight);
     ~Layer();
 
     static uint32_t computeIdealWidth(uint32_t layerWidth);
@@ -85,8 +86,7 @@
         regionRect.translate(layer.left, layer.top);
     }
 
-    void updateDeferred(RenderNode* displayList,
-            int left, int top, int right, int bottom);
+    void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom);
 
     inline uint32_t getWidth() const {
         return texture.width;
@@ -297,7 +297,7 @@
      */
     bool deferredUpdateScheduled;
     OpenGLRenderer* renderer;
-    sp<RenderNode> displayList;
+    sp<RenderNode> renderNode;
     Rect dirtyRect;
     bool debugDrawUpdate;
     bool hasDrawnSinceUpdate;
@@ -307,6 +307,8 @@
 
     Caches& caches;
 
+    RenderState& renderState;
+
     /**
      * Name of the FBO used to render the layer. If the name is 0
      * this layer is not backed by an FBO, but a simple texture.
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 6be0146..13869aa 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -91,7 +91,7 @@
     mCache.clear();
 }
 
-Layer* LayerCache::get(const uint32_t width, const uint32_t height) {
+Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uint32_t height) {
     Layer* layer = NULL;
 
     LayerEntry entry(width, height);
@@ -108,7 +108,7 @@
     } else {
         LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight);
 
-        layer = new Layer(entry.mWidth, entry.mHeight);
+        layer = new Layer(renderState, entry.mWidth, entry.mHeight);
         layer->setBlend(true);
         layer->setEmpty(true);
         layer->setFbo(0);
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 221bfe0..1b0fc2c 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -24,6 +24,8 @@
 namespace android {
 namespace uirenderer {
 
+class RenderState;
+
 ///////////////////////////////////////////////////////////////////////////////
 // Defines
 ///////////////////////////////////////////////////////////////////////////////
@@ -55,7 +57,7 @@
      * @param width The desired width of the layer
      * @param height The desired height of the layer
      */
-    Layer* get(const uint32_t width, const uint32_t height);
+    Layer* get(RenderState& renderState, const uint32_t width, const uint32_t height);
 
     /**
      * Adds the layer to the cache. The layer will not be added if there is
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index df9aee5..873baf5 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -20,6 +20,7 @@
 
 #include <private/hwui/DrawGlInfo.h>
 
+#include "RenderState.h"
 #include "LayerCache.h"
 #include "LayerRenderer.h"
 #include "Matrix.h"
@@ -33,7 +34,9 @@
 // Rendering
 ///////////////////////////////////////////////////////////////////////////////
 
-LayerRenderer::LayerRenderer(Layer* layer): mLayer(layer) {
+LayerRenderer::LayerRenderer(RenderState& renderState, Layer* layer)
+        : OpenGLRenderer(renderState)
+        , mLayer(layer) {
 }
 
 LayerRenderer::~LayerRenderer() {
@@ -43,7 +46,7 @@
         bool opaque) {
     LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo());
 
-    glBindFramebuffer(GL_FRAMEBUFFER, mLayer->getFbo());
+    renderState().bindFramebuffer(mLayer->getFbo());
 
     const float width = mLayer->layer.getWidth();
     const float height = mLayer->layer.getHeight();
@@ -180,7 +183,7 @@
 // Layers management
 ///////////////////////////////////////////////////////////////////////////////
 
-Layer* LayerRenderer::createRenderLayer(uint32_t width, uint32_t height) {
+Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height) {
     LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
 
     Caches& caches = Caches::getInstance();
@@ -191,7 +194,7 @@
     }
 
     caches.activeTexture(0);
-    Layer* layer = caches.layerCache.get(width, height);
+    Layer* layer = caches.layerCache.get(renderState, width, height);
     if (!layer) {
         ALOGW("Could not obtain a layer");
         return NULL;
@@ -221,10 +224,9 @@
     layer->setDirty(true);
     layer->region.clear();
 
-    GLuint previousFbo;
-    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+    GLuint previousFbo = renderState.getFramebuffer();
 
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    renderState.bindFramebuffer(layer->getFbo());
     layer->bindTexture();
 
     // Initialize the texture if needed
@@ -235,7 +237,7 @@
         // This should only happen if we run out of memory
         if (glGetError() != GL_NO_ERROR) {
             ALOGE("Could not allocate texture for layer (fbo=%d %dx%d)", fbo, width, height);
-            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+            renderState.bindFramebuffer(previousFbo);
             caches.resourceCache.decrementRefcount(layer);
             return NULL;
         }
@@ -244,7 +246,7 @@
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
             layer->getTexture(), 0);
 
-    glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+    renderState.bindFramebuffer(previousFbo);
 
     return layer;
 }
@@ -265,10 +267,10 @@
     return true;
 }
 
-Layer* LayerRenderer::createTextureLayer() {
+Layer* LayerRenderer::createTextureLayer(RenderState& renderState) {
     LAYER_RENDERER_LOGD("Creating new texture layer");
 
-    Layer* layer = new Layer(0, 0);
+    Layer* layer = new Layer(renderState, 0, 0);
     layer->setCacheable(false);
     layer->setTextureLayer(true);
     layer->setEmpty(true);
@@ -332,27 +334,32 @@
     }
 }
 
-void LayerRenderer::flushLayer(Layer* layer) {
+void LayerRenderer::flushLayer(RenderState& renderState, Layer* layer) {
 #ifdef GL_EXT_discard_framebuffer
+    if (!layer) return;
+
     GLuint fbo = layer->getFbo();
-    if (layer && fbo) {
+    if (fbo) {
         // If possible, discard any enqueud operations on deferred
         // rendering architectures
         if (Extensions::getInstance().hasDiscardFramebuffer()) {
-            GLuint previousFbo;
-            glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
-            if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+            GLuint previousFbo = renderState.getFramebuffer();
+            if (fbo != previousFbo) {
+                renderState.bindFramebuffer(fbo);
+            }
 
             const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 };
             glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
 
-            if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+            if (fbo != previousFbo) {
+                renderState.bindFramebuffer(previousFbo);
+            }
         }
     }
 #endif
 }
 
-bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
+bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap) {
     Caches& caches = Caches::getInstance();
     if (layer && bitmap->width() <= caches.maxTextureSize &&
             bitmap->height() <= caches.maxTextureSize) {
@@ -367,7 +374,8 @@
 
         GLuint texture;
         GLuint previousFbo;
-        GLuint previousViewport[4];
+        GLsizei previousViewportWidth;
+        GLsizei previousViewportHeight;
 
         GLenum format;
         GLenum type;
@@ -402,9 +410,9 @@
         layer->setAlpha(255, SkXfermode::kSrc_Mode);
         layer->setFbo(fbo);
 
-        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
-        glGetIntegerv(GL_VIEWPORT, (GLint*) &previousViewport);
-        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        previousFbo = renderState.getFramebuffer();
+        renderState.getViewport(&previousViewportWidth, &previousViewportHeight);
+        renderState.bindFramebuffer(fbo);
 
         glGenTextures(1, &texture);
         if ((error = glGetError()) != GL_NO_ERROR) goto error;
@@ -429,7 +437,7 @@
         if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
         {
-            LayerRenderer renderer(layer);
+            LayerRenderer renderer(renderState, layer);
             renderer.setViewport(bitmap->width(), bitmap->height());
             renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
                     bitmap->width(), bitmap->height(), !layer->isBlend());
@@ -469,13 +477,12 @@
         }
 #endif
 
-        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+        renderState.bindFramebuffer(previousFbo);
         layer->setAlpha(alpha, mode);
         layer->setFbo(previousLayerFbo);
         caches.deleteTexture(texture);
         caches.fboCache.put(fbo);
-        glViewport(previousViewport[0], previousViewport[1],
-                previousViewport[2], previousViewport[3]);
+        renderState.setViewport(previousViewportWidth, previousViewportHeight);
 
         return status;
     }
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index e79e7b8..9202e49 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -27,6 +27,8 @@
 namespace android {
 namespace uirenderer {
 
+class RenderState;
+
 ///////////////////////////////////////////////////////////////////////////////
 // Defines
 ///////////////////////////////////////////////////////////////////////////////
@@ -44,7 +46,7 @@
 
 class LayerRenderer: public OpenGLRenderer {
 public:
-    ANDROID_API LayerRenderer(Layer* layer);
+    LayerRenderer(RenderState& renderState, Layer* layer);
     virtual ~LayerRenderer();
 
     virtual void onViewportInitialized(int width, int height) { /* do nothing */ }
@@ -52,16 +54,16 @@
     virtual status_t clear(float left, float top, float right, float bottom, bool opaque);
     virtual void finish();
 
-    ANDROID_API static Layer* createTextureLayer();
-    ANDROID_API static Layer* createRenderLayer(uint32_t width, uint32_t height);
-    ANDROID_API static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
-    ANDROID_API static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
+    static Layer* createTextureLayer(RenderState& renderState);
+    static Layer* createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height);
+    static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
+    static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
             bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform);
-    ANDROID_API static void destroyLayer(Layer* layer);
+    static void destroyLayer(Layer* layer);
     ANDROID_API static void destroyLayerDeferred(Layer* layer);
-    ANDROID_API static bool copyLayer(Layer* layer, SkBitmap* bitmap);
+    static bool copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap);
 
-    static void flushLayer(Layer* layer);
+    static void flushLayer(RenderState& renderState, Layer* layer);
 
 protected:
     virtual void ensureStencilBuffer();
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..ede89d7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -35,6 +35,7 @@
 #include "DeferredDisplayList.h"
 #include "DisplayListRenderer.h"
 #include "Fence.h"
+#include "RenderState.h"
 #include "PathTessellator.h"
 #include "Properties.h"
 #include "ShadowTessellator.h"
@@ -129,8 +130,10 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-OpenGLRenderer::OpenGLRenderer():
-        mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
+OpenGLRenderer::OpenGLRenderer(RenderState& renderState)
+        : mCaches(Caches::getInstance())
+        , mExtensions(Extensions::getInstance())
+        , mRenderState(renderState) {
     // *set* draw modifiers to be 0
     memset(&mDrawModifiers, 0, sizeof(mDrawModifiers));
     mDrawModifiers.mOverrideLayerAlpha = 1.0f;
@@ -187,7 +190,7 @@
 
     discardFramebuffer(mTilingClip.left, mTilingClip.top, mTilingClip.right, mTilingClip.bottom);
 
-    glViewport(0, 0, getWidth(), getHeight());
+    mRenderState.setViewport(getWidth(), getHeight());
 
     // Functors break the tiling extension in pretty spectacular ways
     // This ensures we don't use tiling when a functor is going to be
@@ -311,46 +314,9 @@
     mFrameStarted = false;
 }
 
-void OpenGLRenderer::interrupt() {
-    if (mCaches.currentProgram) {
-        if (mCaches.currentProgram->isInUse()) {
-            mCaches.currentProgram->remove();
-            mCaches.currentProgram = NULL;
-        }
-    }
-    mCaches.resetActiveTexture();
-    mCaches.unbindMeshBuffer();
-    mCaches.unbindIndicesBuffer();
-    mCaches.resetVertexPointers();
-    mCaches.disableTexCoordsVertexArray();
-    debugOverdraw(false, false);
-}
-
-void OpenGLRenderer::resume() {
-    const Snapshot* snapshot = currentSnapshot();
-    glViewport(0, 0, getViewportWidth(), getViewportHeight());
-    glBindFramebuffer(GL_FRAMEBUFFER, snapshot->fbo);
-    debugOverdraw(true, false);
-
-    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-
-    mCaches.scissorEnabled = glIsEnabled(GL_SCISSOR_TEST);
-    mCaches.enableScissor();
-    mCaches.resetScissor();
-    dirtyClip();
-
-    mCaches.activeTexture(0);
-    mCaches.resetBoundTextures();
-
-    mCaches.blend = true;
-    glEnable(GL_BLEND);
-    glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
-    glBlendEquation(GL_FUNC_ADD);
-}
-
 void OpenGLRenderer::resumeAfterLayer() {
-    glViewport(0, 0, getViewportWidth(), getViewportHeight());
-    glBindFramebuffer(GL_FRAMEBUFFER, currentSnapshot()->fbo);
+    mRenderState.setViewport(getViewportWidth(), getViewportHeight());
+    mRenderState.bindFramebuffer(currentSnapshot()->fbo);
     debugOverdraw(true, false);
 
     mCaches.resetScissor();
@@ -379,20 +345,19 @@
     info.height = getViewportHeight();
     currentTransform()->copyTo(&info.transform[0]);
 
-    bool dirtyClip = mDirtyClip;
+    bool prevDirtyClip = mDirtyClip;
     // setup GL state for functor
     if (mDirtyClip) {
         setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt()
     }
-    if (mCaches.enableScissor() || dirtyClip) {
+    if (mCaches.enableScissor() || prevDirtyClip) {
         setScissorFromClip();
     }
-    interrupt();
 
-    // call functor immediately after GL state setup
-    (*functor)(DrawGlInfo::kModeDraw, &info);
+    mRenderState.invokeFunctor(functor, DrawGlInfo::kModeDraw, &info);
+    // Scissor may have been modified, reset dirty clip
+    dirtyClip();
 
-    resume();
     return DrawGlInfo::kStatusDrew;
 }
 
@@ -413,17 +378,7 @@
 }
 
 void OpenGLRenderer::debugOverdraw(bool enable, bool clear) {
-    if (mCaches.debugOverdraw && getTargetFbo() == 0) {
-        if (clear) {
-            mCaches.disableScissor();
-            mCaches.stencil.clear();
-        }
-        if (enable) {
-            mCaches.stencil.enableDebugWrite();
-        } else {
-            mCaches.stencil.disable();
-        }
-    }
+    mRenderState.debugOverdraw(enable, clear);
 }
 
 void OpenGLRenderer::renderOverdraw() {
@@ -474,8 +429,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
-    if (layer->deferredUpdateScheduled && layer->renderer &&
-            layer->displayList.get() && layer->displayList->isRenderable()) {
+    if (layer->deferredUpdateScheduled && layer->renderer
+            && layer->renderNode.get() && layer->renderNode->isRenderable()) {
         ATRACE_CALL();
 
         Rect& dirty = layer->dirtyRect;
@@ -528,7 +483,7 @@
 
         if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
             mLayerUpdates.clear();
-            glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+            mRenderState.bindFramebuffer(getTargetFbo());
         }
         endMark();
     }
@@ -556,7 +511,7 @@
         }
 
         mLayerUpdates.clear();
-        glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+        mRenderState.bindFramebuffer(getTargetFbo());
 
         endMark();
     }
@@ -620,7 +575,7 @@
     bool restoreLayer = removed.flags & Snapshot::kFlagIsLayer;
 
     if (restoreViewport) {
-        glViewport(0, 0, getViewportWidth(), getViewportHeight());
+        mRenderState.setViewport(getViewportWidth(), getViewportHeight());
     }
 
     if (restoreClip) {
@@ -791,7 +746,7 @@
     }
 
     mCaches.activeTexture(0);
-    Layer* layer = mCaches.layerCache.get(bounds.getWidth(), bounds.getHeight());
+    Layer* layer = mCaches.layerCache.get(mRenderState, bounds.getWidth(), bounds.getHeight());
     if (!layer) {
         return false;
     }
@@ -853,7 +808,7 @@
     endTiling();
     debugOverdraw(false, false);
     // Bind texture to FBO
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    mRenderState.bindFramebuffer(layer->getFbo());
     layer->bindTexture();
 
     // Initialize the texture if needed
@@ -876,7 +831,7 @@
     dirtyClip();
 
     // Change the ortho projection
-    glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+    mRenderState.setViewport(bounds.getWidth(), bounds.getHeight());
     return true;
 }
 
@@ -907,7 +862,7 @@
         layer->removeFbo(false);
 
         // Unbind current FBO and restore previous one
-        glBindFramebuffer(GL_FRAMEBUFFER, restored.fbo);
+        mRenderState.bindFramebuffer(restored.fbo);
         debugOverdraw(true, false);
 
         startTilingCurrentClip();
@@ -1931,25 +1886,24 @@
 // Drawing
 ///////////////////////////////////////////////////////////////////////////////
 
-status_t OpenGLRenderer::drawDisplayList(RenderNode* displayList, Rect& dirty,
-        int32_t replayFlags) {
+status_t OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
     status_t status;
     // All the usual checks and setup operations (quickReject, setupDraw, etc.)
     // will be performed by the display list itself
-    if (displayList && displayList->isRenderable()) {
+    if (renderNode && renderNode->isRenderable()) {
         // compute 3d ordering
-        displayList->computeOrdering();
+        renderNode->computeOrdering();
         if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
             status = startFrame();
             ReplayStateStruct replayStruct(*this, dirty, replayFlags);
-            displayList->replayNodeTree(replayStruct);
+            renderNode->replay(replayStruct, 0);
             return status | replayStruct.mDrawGlStatus;
         }
 
         bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs!
         DeferredDisplayList deferredList(*currentClipRect(), avoidOverdraw);
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
-        displayList->deferNodeTree(deferStruct);
+        renderNode->defer(deferStruct, 0);
 
         flushLayers();
         status = startFrame();
@@ -2043,10 +1997,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)) {
@@ -2556,8 +2510,8 @@
         return drawShape(left, top, texture, p);
     }
 
-    const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect(*currentTransform(),
-            right - left, bottom - top, rx, ry, p);
+    const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect(
+            *currentTransform(), *p, right - left, bottom - top, rx, ry);
     return drawVertexBuffer(left, top, *vertexBuffer, p);
 }
 
@@ -2611,10 +2565,6 @@
         return DrawGlInfo::kStatusDone;
     }
 
-    if (fabs(sweepAngle) >= 360.0f) {
-        return drawOval(left, top, right, bottom, p);
-    }
-
     // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
     if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) {
         mCaches.activeTexture(0);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b9b369f..8a1aebc 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -57,6 +57,7 @@
 namespace uirenderer {
 
 class DeferredDisplayState;
+class RenderState;
 class RenderNode;
 class TextSetupFunctor;
 class VertexBuffer;
@@ -119,33 +120,31 @@
  */
 class OpenGLRenderer : public StatefulBaseRenderer {
 public:
-    ANDROID_API OpenGLRenderer();
+    OpenGLRenderer(RenderState& renderState);
     virtual ~OpenGLRenderer();
 
-    ANDROID_API void initProperties();
+    void initProperties();
 
     virtual void onViewportInitialized();
     virtual status_t prepareDirty(float left, float top, float right, float bottom, bool opaque);
     virtual void finish();
-    virtual void interrupt();
-    virtual void resume();
 
-    ANDROID_API void setCountOverdrawEnabled(bool enabled) {
+    void setCountOverdrawEnabled(bool enabled) {
         mCountOverdraw = enabled;
     }
 
-    ANDROID_API float getOverdraw() {
+    float getOverdraw() {
         return mCountOverdraw ? mOverdraw : 0.0f;
     }
 
     virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty);
 
-    ANDROID_API void pushLayerUpdate(Layer* layer);
-    ANDROID_API void cancelLayerUpdate(Layer* layer);
-    ANDROID_API void clearLayerUpdates();
-    ANDROID_API void flushLayerUpdates();
+    void pushLayerUpdate(Layer* layer);
+    void cancelLayerUpdate(Layer* layer);
+    void clearLayerUpdates();
+    void flushLayerUpdates();
 
-    ANDROID_API virtual int saveLayer(float left, float top, float right, float bottom,
+    virtual int saveLayer(float left, float top, float right, float bottom,
             const SkPaint* paint, int flags) {
         return saveLayer(left, top, right, bottom, paint, flags, NULL);
     }
@@ -158,13 +157,13 @@
     int saveLayerDeferred(float left, float top, float right, float bottom,
             const SkPaint* paint, int flags);
 
-    virtual status_t drawDisplayList(RenderNode* displayList, Rect& dirty, int32_t replayFlags = 1);
+    virtual status_t drawRenderNode(RenderNode* displayList, Rect& dirty, int32_t replayFlags = 1);
     virtual status_t drawLayer(Layer* layer, float x, float y);
     virtual status_t drawBitmap(const SkBitmap* bitmap, float left, float top,
             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,
@@ -185,12 +184,6 @@
     virtual status_t drawRoundRect(float left, float top, float right, float bottom,
             float rx, float ry, const SkPaint* paint);
     virtual status_t drawCircle(float x, float y, float radius, const SkPaint* paint);
-    virtual status_t drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
-            CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
-        // TODO: Remove once android_view_GLES20Canvas uses DisplayListRenderer
-        // directly
-        return drawCircle(x->value, y->value, radius->value, &paint->value);
-    }
     virtual status_t drawOval(float left, float top, float right, float bottom,
             const SkPaint* paint);
     virtual status_t drawArc(float left, float top, float right, float bottom,
@@ -231,7 +224,7 @@
     const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
     void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
 
-    ANDROID_API bool isCurrentTransformSimple() {
+    bool isCurrentTransformSimple() {
         return currentTransform()->isSimple();
     }
 
@@ -474,6 +467,8 @@
         return false;
     }
 
+    inline RenderState& renderState() { return mRenderState; }
+
 private:
     /**
      * Discards the content of the framebuffer if supported by the driver.
@@ -977,6 +972,7 @@
     // Various caches
     Caches& mCaches;
     Extensions& mExtensions;
+    RenderState& mRenderState;
 
     // List of rectangles to clear after saveLayer() is invoked
     Vector<Rect*> mLayers;
@@ -1012,7 +1008,6 @@
 
     bool mSkipOutlineClip;
 
-    friend class DisplayListRenderer;
     friend class Layer;
     friend class TextSetupFunctor;
     friend class DrawBitmapOp;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index ab6b742..97eb583 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -104,8 +104,7 @@
 }
 
 static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
-    bitmap.setConfig(SkBitmap::kA8_Config, width, height);
-    bitmap.allocPixels();
+    bitmap.allocPixels(SkImageInfo::MakeA8(width, height));
     bitmap.eraseColor(0);
 }
 
@@ -339,7 +338,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 +349,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/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index a58e3e7..310b107 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -57,17 +57,26 @@
 #define ROUND_CAP_THRESH 0.25f
 #define PI 3.1415926535897932f
 
+// temporary error thresholds
+#define ERROR_DEPTH 20
+#define ERROR_SCALE 1e10
+#define ERROR_SQR_INV_THRESH 1e-20
+
 void PathTessellator::extractTessellationScales(const Matrix4& transform,
         float* scaleX, float* scaleY) {
-    *scaleX = 1.0f;
-    *scaleY = 1.0f;
-    if (CC_UNLIKELY(!transform.isPureTranslate())) {
+    if (CC_LIKELY(transform.isPureTranslate())) {
+        *scaleX = 1.0f;
+        *scaleY = 1.0f;
+    } else {
         float m00 = transform.data[Matrix4::kScaleX];
         float m01 = transform.data[Matrix4::kSkewY];
         float m10 = transform.data[Matrix4::kSkewX];
         float m11 = transform.data[Matrix4::kScaleY];
         *scaleX = sqrt(m00 * m00 + m01 * m01);
         *scaleY = sqrt(m10 * m10 + m11 * m11);
+
+        LOG_ALWAYS_FATAL_IF(*scaleX > ERROR_SCALE || *scaleY > ERROR_SCALE,
+                "scales %e x %e too large for tessellation", *scaleX, *scaleY);
     }
 }
 
@@ -92,10 +101,12 @@
 public:
     PaintInfo(const SkPaint* paint, const mat4& transform) :
             style(paint->getStyle()), cap(paint->getStrokeCap()), isAA(paint->isAntiAlias()),
-            inverseScaleX(1.0f), inverseScaleY(1.0f),
             halfStrokeWidth(paint->getStrokeWidth() * 0.5f), maxAlpha(1.0f) {
         // compute inverse scales
-        if (CC_UNLIKELY(!transform.isPureTranslate())) {
+        if (CC_LIKELY(transform.isPureTranslate())) {
+            inverseScaleX = 1.0f;
+            inverseScaleY = 1.0f;
+        } else {
             float scaleX, scaleY;
             PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
             inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
@@ -922,6 +933,9 @@
         Vector<Vertex>& outputVertices) {
     ATRACE_CALL();
 
+    LOG_ALWAYS_FATAL_IF(sqrInvScaleX < ERROR_SQR_INV_THRESH || sqrInvScaleY < ERROR_SQR_INV_THRESH,
+            "Invalid scale factors used for approx %e, %e", sqrInvScaleX, sqrInvScaleY);
+
     // TODO: to support joins other than sharp miter, join vertices should be labelled in the
     // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
     SkPath::Iter iter(path, forceClose);
@@ -975,14 +989,14 @@
 // Bezier approximation
 ///////////////////////////////////////////////////////////////////////////////
 
-// Depth at which recursion is aborted
-#define ABORT_DEPTH 10
-
 void PathTessellator::recursiveCubicBezierVertices(
         float p1x, float p1y, float c1x, float c1y,
         float p2x, float p2y, float c2x, float c2y,
         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
         Vector<Vertex>& outputVertices, int depth) {
+    LOG_ALWAYS_FATAL_IF(depth >= ERROR_DEPTH, "ERROR DEPTH exceeded: cubic approx, invscale %e x %e, vertcount %d",
+            sqrInvScaleX, sqrInvScaleY, outputVertices.size());
+
     float dx = p2x - p1x;
     float dy = p2y - p1y;
     float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
@@ -990,8 +1004,7 @@
     float d = d1 + d2;
 
     // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
-
-    if (depth >= ABORT_DEPTH || d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+    if (d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
         // below thresh, draw line by adding endpoint
         pushToVector(outputVertices, p2x, p2y);
     } else {
@@ -1029,11 +1042,14 @@
         float cx, float cy,
         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
         Vector<Vertex>& outputVertices, int depth) {
+    LOG_ALWAYS_FATAL_IF(depth >= ERROR_DEPTH, "ERROR_DEPTH exceeded: quadratic approx, invscale %e x %e, vertcount %d",
+            sqrInvScaleX, sqrInvScaleY, outputVertices.size());
+
     float dx = bx - ax;
     float dy = by - ay;
     float d = (cx - bx) * dy - (cy - by) * dx;
 
-    if (depth >= ABORT_DEPTH || d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+    if (d * d < thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
         // below thresh, draw line by adding endpoint
         pushToVector(outputVertices, bx, by);
     } else {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d964efc..e803ec3 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -15,19 +15,24 @@
  */
 
 #define ATRACE_TAG ATRACE_TAG_VIEW
+#define LOG_TAG "OpenGLRenderer"
 
 #include "RenderNode.h"
 
 #include <algorithm>
+#include <string>
 
 #include <SkCanvas.h>
 #include <algorithm>
 
 #include <utils/Trace.h>
 
+#include "DamageAccumulator.h"
 #include "Debug.h"
 #include "DisplayListOp.h"
 #include "DisplayListLogBuffer.h"
+#include "LayerRenderer.h"
+#include "OpenGLRenderer.h"
 #include "utils/MathUtils.h"
 
 namespace android {
@@ -57,12 +62,14 @@
         , mNeedsDisplayListDataSync(false)
         , mDisplayListData(0)
         , mStagingDisplayListData(0)
-        , mNeedsAnimatorsSync(false) {
+        , mAnimatorManager(*this)
+        , mLayer(0) {
 }
 
 RenderNode::~RenderNode() {
     delete mDisplayListData;
     delete mStagingDisplayListData;
+    LayerRenderer::destroyLayerDeferred(mLayer);
 }
 
 void RenderNode::setStagingDisplayList(DisplayListData* data) {
@@ -110,84 +117,147 @@
     prepareTreeImpl(info);
 }
 
-void RenderNode::prepareTreeImpl(TreeInfo& info) {
-    if (info.performStagingPush) {
-        pushStagingChanges(info);
-    }
-    if (info.evaluateAnimations) {
-        evaluateAnimations(info);
-    }
-    prepareSubTree(info, mDisplayListData);
+void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+    mAnimatorManager.addAnimator(animator);
 }
 
-class PushAnimatorsFunctor {
-public:
-    PushAnimatorsFunctor(RenderNode* target, TreeInfo& info)
-            : mTarget(target), mInfo(info) {}
-
-    bool operator() (const sp<BaseRenderNodeAnimator>& animator) {
-        animator->setupStartValueIfNecessary(mTarget, mInfo);
-        return animator->isFinished();
+void RenderNode::damageSelf(TreeInfo& info) {
+    if (isRenderable()) {
+        if (properties().getClipDamageToBounds()) {
+            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);
+        }
     }
-private:
-    RenderNode* mTarget;
-    TreeInfo& mInfo;
-};
+}
 
-void RenderNode::pushStagingChanges(TreeInfo& info) {
+void RenderNode::prepareLayer(TreeInfo& info) {
+    LayerType layerType = properties().layerProperties().type();
+    if (CC_UNLIKELY(layerType == kLayerTypeRenderLayer)) {
+        // We push a null transform here as we don't care what the existing dirty
+        // area is, only what our display list dirty is as well as our children's
+        // dirty area
+        info.damageAccumulator->pushNullTransform();
+    }
+}
+
+void RenderNode::pushLayerUpdate(TreeInfo& info) {
+    LayerType layerType = properties().layerProperties().type();
+    // If we are not a layer OR we cannot be rendered (eg, view was detached)
+    // we need to destroy any Layers we may have had previously
+    if (CC_LIKELY(layerType != kLayerTypeRenderLayer) || CC_UNLIKELY(!isRenderable())) {
+        if (layerType == kLayerTypeRenderLayer) {
+            info.damageAccumulator->popTransform();
+        }
+        if (CC_UNLIKELY(mLayer)) {
+            LayerRenderer::destroyLayer(mLayer);
+            mLayer = NULL;
+        }
+        return;
+    }
+
+    if (!mLayer) {
+        mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
+        applyLayerPropertiesToLayer(info);
+        damageSelf(info);
+    } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
+        if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
+            LayerRenderer::destroyLayer(mLayer);
+            mLayer = 0;
+        }
+        damageSelf(info);
+    }
+
+    SkRect dirty;
+    info.damageAccumulator->peekAtDirty(&dirty);
+    info.damageAccumulator->popTransform();
+
+    if (!mLayer) {
+        if (info.errorHandler) {
+            std::string msg = "Unable to create layer for ";
+            msg += getName();
+            info.errorHandler->onError(msg);
+        }
+        return;
+    }
+
+    if (!dirty.isEmpty()) {
+        mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
+    }
+    // This is not inside the above if because we may have called
+    // updateDeferred on a previous prepare pass that didn't have a renderer
+    if (info.renderer && mLayer->deferredUpdateScheduled) {
+        info.renderer->pushLayerUpdate(mLayer);
+    }
+}
+
+void RenderNode::prepareTreeImpl(TreeInfo& info) {
+    info.damageAccumulator->pushTransform(this);
+    if (info.mode == TreeInfo::MODE_FULL) {
+        pushStagingPropertiesChanges(info);
+        mAnimatorManager.animate(info);
+    } else if (info.mode == TreeInfo::MODE_MAYBE_DETACHING) {
+        pushStagingPropertiesChanges(info);
+    } else if (info.mode == TreeInfo::MODE_RT_ONLY) {
+        mAnimatorManager.animate(info);
+    }
+
+    prepareLayer(info);
+    if (info.mode == TreeInfo::MODE_FULL) {
+        pushStagingDisplayListChanges(info);
+    }
+    prepareSubTree(info, mDisplayListData);
+    pushLayerUpdate(info);
+
+    info.damageAccumulator->popTransform();
+}
+
+void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
     // Push the animators first so that setupStartValueIfNecessary() is called
     // before properties() is trampled by stagingProperties(), as they are
     // required by some animators.
-    if (mNeedsAnimatorsSync) {
-        mAnimators.resize(mStagingAnimators.size());
-        std::vector< sp<BaseRenderNodeAnimator> >::iterator it;
-        PushAnimatorsFunctor functor(this, info);
-        // hint: this means copy_if_not()
-        it = std::remove_copy_if(mStagingAnimators.begin(), mStagingAnimators.end(),
-                mAnimators.begin(), functor);
-        mAnimators.resize(std::distance(mAnimators.begin(), it));
-    }
+    mAnimatorManager.pushStaging(info);
     if (mDirtyPropertyFields) {
         mDirtyPropertyFields = 0;
+        damageSelf(info);
+        info.damageAccumulator->popTransform();
         mProperties = mStagingProperties;
+        applyLayerPropertiesToLayer(info);
+        // 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);
     }
+}
+
+void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
+    if (CC_LIKELY(!mLayer)) return;
+
+    const LayerProperties& props = properties().layerProperties();
+    mLayer->setAlpha(props.alpha(), props.xferMode());
+    mLayer->setColorFilter(props.colorFilter());
+    mLayer->setBlend(props.needsBlending());
+}
+
+void RenderNode::pushStagingDisplayListChanges(TreeInfo& 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, info);
         prepareSubTree(oldTreeInfo, mDisplayListData);
-        // TODO: The damage for the old tree should be accounted for
         delete mDisplayListData;
         mDisplayListData = mStagingDisplayListData;
         mStagingDisplayListData = 0;
+        damageSelf(info);
     }
 }
 
-class AnimateFunctor {
-public:
-    AnimateFunctor(RenderNode* target, TreeInfo& info)
-            : mTarget(target), mInfo(info) {}
-
-    bool operator() (const sp<BaseRenderNodeAnimator>& animator) {
-        return animator->animate(mTarget, mInfo);
-    }
-private:
-    RenderNode* mTarget;
-    TreeInfo& mInfo;
-};
-
-void RenderNode::evaluateAnimations(TreeInfo& info) {
-    if (!mAnimators.size()) return;
-
-    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();
-}
-
 void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
     if (subtree) {
         TextureCache& cache = Caches::getInstance().textureCache;
@@ -201,8 +271,11 @@
             info.prepareTextures = cache.prefetchAndMarkInUse(subtree->bitmapResources[i]);
         }
         for (size_t i = 0; i < subtree->children().size(); i++) {
-            RenderNode* childNode = subtree->children()[i]->mDisplayList;
+            DrawRenderNodeOp* op = subtree->children()[i];
+            RenderNode* childNode = op->mRenderNode;
+            info.damageAccumulator->pushTransform(&op->mTransformFromParent);
             childNode->prepareTreeImpl(info);
+            info.damageAccumulator->popTransform();
         }
     }
 }
@@ -223,9 +296,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()) {
@@ -234,9 +307,10 @@
             renderer.concatMatrix(*properties().getTransformMatrix());
         }
     }
-    bool clipToBoundsNeeded = properties().getCaching() ? false : properties().getClipToBounds();
+    const bool isLayer = properties().layerProperties().type() != kLayerTypeNone;
+    bool clipToBoundsNeeded = isLayer ? false : properties().getClipToBounds();
     if (properties().getAlpha() < 1) {
-        if (properties().getCaching()) {
+        if (isLayer) {
             renderer.setOverrideLayerAlpha(properties().getAlpha());
         } else if (!properties().getHasOverlappingRendering()) {
             renderer.scaleAlpha(properties().getAlpha());
@@ -329,16 +403,16 @@
     // transform properties are applied correctly to top level children
     if (mDisplayListData == NULL) return;
     for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
-        DrawDisplayListOp* childOp = mDisplayListData->children()[i];
-        childOp->mDisplayList->computeOrderingImpl(childOp,
+        DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+        childOp->mRenderNode->computeOrderingImpl(childOp,
                 properties().getOutline().getPath(), &mProjectedNodes, &mat4::identity());
     }
 }
 
 void RenderNode::computeOrderingImpl(
-        DrawDisplayListOp* opState,
+        DrawRenderNodeOp* opState,
         const SkPath* outlineOfProjectionSurface,
-        Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
+        Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
         const mat4* transformFromProjectionSurface) {
     mProjectedNodes.clear();
     if (mDisplayListData == NULL || mDisplayListData->isEmpty()) return;
@@ -362,11 +436,11 @@
         const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0;
         bool haveAppliedPropertiesToProjection = false;
         for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
-            DrawDisplayListOp* childOp = mDisplayListData->children()[i];
-            RenderNode* child = childOp->mDisplayList;
+            DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+            RenderNode* child = childOp->mRenderNode;
 
             const SkPath* projectionOutline = NULL;
-            Vector<DrawDisplayListOp*>* projectionChildren = NULL;
+            Vector<DrawRenderNodeOp*>* projectionChildren = NULL;
             const mat4* projectionTransform = NULL;
             if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
                 // if receiving projections, collect projecting descendent
@@ -410,15 +484,7 @@
     const int mLevel;
 };
 
-void RenderNode::deferNodeTree(DeferStateStruct& deferStruct) {
-    DeferOperationHandler handler(deferStruct, 0);
-    if (MathUtils::isPositive(properties().getZ())) {
-        issueDrawShadowOperation(Matrix4::identity(), handler);
-    }
-    issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
-}
-
-void RenderNode::deferNodeInParent(DeferStateStruct& deferStruct, const int level) {
+void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
     DeferOperationHandler handler(deferStruct, level);
     issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
 }
@@ -448,29 +514,21 @@
     const int mLevel;
 };
 
-void RenderNode::replayNodeTree(ReplayStateStruct& replayStruct) {
-    ReplayOperationHandler handler(replayStruct, 0);
-    if (MathUtils::isPositive(properties().getZ())) {
-        issueDrawShadowOperation(Matrix4::identity(), handler);
-    }
-    issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
-}
-
-void RenderNode::replayNodeInParent(ReplayStateStruct& replayStruct, const int level) {
+void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
     ReplayOperationHandler handler(replayStruct, level);
     issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
 }
 
-void RenderNode::buildZSortedChildList(Vector<ZDrawDisplayListOpPair>& zTranslatedNodes) {
+void RenderNode::buildZSortedChildList(Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
     if (mDisplayListData == NULL || mDisplayListData->children().size() == 0) return;
 
     for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
-        DrawDisplayListOp* childOp = mDisplayListData->children()[i];
-        RenderNode* child = childOp->mDisplayList;
+        DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+        RenderNode* child = childOp->mRenderNode;
         float childZ = child->properties().getZ();
 
         if (!MathUtils::isZero(childZ)) {
-            zTranslatedNodes.add(ZDrawDisplayListOpPair(childZ, childOp));
+            zTranslatedNodes.add(ZDrawRenderNodeOpPair(childZ, childOp));
             childOp->mSkipInOrderDraw = true;
         } else if (!child->properties().getProjectBackwards()) {
             // regular, in order drawing DisplayList
@@ -515,10 +573,40 @@
     handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
 }
 
+template <class T>
+int RenderNode::issueOperationsOfNegZChildren(
+        const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+        OpenGLRenderer& renderer, T& handler) {
+    if (zTranslatedNodes.isEmpty()) return -1;
+
+    // create a save around the body of the ViewGroup's draw method, so that
+    // matrix/clip methods don't affect composited children
+    int shadowSaveCount = renderer.getSaveCount();
+    handler(new (handler.allocator()) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+            PROPERTY_SAVECOUNT, properties().getClipToBounds());
+
+    issueOperationsOf3dChildren(zTranslatedNodes, kNegativeZChildren, renderer, handler);
+    return shadowSaveCount;
+}
+
+template <class T>
+void RenderNode::issueOperationsOfPosZChildren(int shadowRestoreTo,
+        const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+        OpenGLRenderer& renderer, T& handler) {
+    if (zTranslatedNodes.isEmpty()) return;
+
+    LOG_ALWAYS_FATAL_IF(shadowRestoreTo < 0, "invalid save to restore to");
+    handler(new (handler.allocator()) RestoreToCountOp(shadowRestoreTo),
+            PROPERTY_SAVECOUNT, properties().getClipToBounds());
+    renderer.setOverrideLayerAlpha(1.0f);
+
+    issueOperationsOf3dChildren(zTranslatedNodes, kPositiveZChildren, renderer, handler);
+}
+
 #define SHADOW_DELTA 0.1f
 
 template <class T>
-void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawDisplayListOpPair>& zTranslatedNodes,
+void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
         ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler) {
     const int size = zTranslatedNodes.size();
     if (size == 0
@@ -553,8 +641,8 @@
     float lastCasterZ = 0.0f;
     while (shadowIndex < endIndex || drawIndex < endIndex) {
         if (shadowIndex < endIndex) {
-            DrawDisplayListOp* casterOp = zTranslatedNodes[shadowIndex].value;
-            RenderNode* caster = casterOp->mDisplayList;
+            DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
+            RenderNode* caster = casterOp->mRenderNode;
             const float casterZ = zTranslatedNodes[shadowIndex].key;
             // attempt to render the shadow if the caster about to be drawn is its caster,
             // OR if its caster's Z value is similar to the previous potential caster
@@ -571,8 +659,8 @@
         // since it modifies the renderer's matrix
         int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
 
-        DrawDisplayListOp* childOp = zTranslatedNodes[drawIndex].value;
-        RenderNode* child = childOp->mDisplayList;
+        DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
+        RenderNode* child = childOp->mRenderNode;
 
         renderer.concatMatrix(childOp->mTransformFromParent);
         childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
@@ -588,8 +676,6 @@
 void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler) {
     DISPLAY_LIST_LOGD("%*s%d projected children:", (handler.level() + 1) * 2, "", mProjectedNodes.size());
     const SkPath* projectionReceiverOutline = properties().getOutline().getPath();
-    bool maskProjecteesWithPath = projectionReceiverOutline != NULL
-            && !projectionReceiverOutline->isRect(NULL);
     int restoreTo = renderer.getSaveCount();
 
     // If the projection reciever has an outline, we mask each of the projected rendernodes to it
@@ -610,7 +696,7 @@
             SaveLayerOp* op = new (alloc) SaveLayerOp(
                     outlineBounds.left(), outlineBounds.top(),
                     outlineBounds.right(), outlineBounds.bottom(),
-                    255, SkCanvas::kARGB_ClipLayer_SaveFlag);
+                    255, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag | SkCanvas::kARGB_ClipLayer_SaveFlag);
             op->setMask(projectionReceiverOutline);
             handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
 
@@ -623,7 +709,7 @@
 
     // draw projected nodes
     for (size_t i = 0; i < mProjectedNodes.size(); i++) {
-        DrawDisplayListOp* childOp = mProjectedNodes[i];
+        DrawRenderNodeOp* childOp = mProjectedNodes[i];
 
         // matrix save, concat, and restore can be done safely without allocating operations
         int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
@@ -651,8 +737,14 @@
  */
 template <class T>
 void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
+    const bool drawLayer = (mLayer && (&renderer != mLayer->renderer));
+    // If we are updating the contents of mLayer, we don't want to apply any of
+    // the RenderNode's properties to this issueOperations pass. Those will all
+    // be applied when the layer is drawn, aka when this is true.
+    const bool useViewProperties = (!mLayer || drawLayer);
+
     const int level = handler.level();
-    if (mDisplayListData->isEmpty() || properties().getAlpha() <= 0) {
+    if (mDisplayListData->isEmpty() || (useViewProperties && properties().getAlpha() <= 0)) {
         DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName());
         return;
     }
@@ -674,7 +766,9 @@
     DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
 
-    setViewProperties<T>(renderer, handler);
+    if (useViewProperties) {
+        setViewProperties<T>(renderer, handler);
+    }
 
     bool quickRejected = properties().getClipToBounds()
             && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
@@ -683,31 +777,36 @@
             renderer.setClippingOutline(alloc, &(mProperties.getOutline()));
         }
 
-        Vector<ZDrawDisplayListOpPair> zTranslatedNodes;
-        buildZSortedChildList(zTranslatedNodes);
+        if (drawLayer) {
+            handler(new (alloc) DrawLayerOp(mLayer, 0, 0),
+                    renderer.getSaveCount() - 1, properties().getClipToBounds());
+        } else {
+            Vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
+            buildZSortedChildList(zTranslatedNodes);
 
-        // for 3d root, draw children with negative z values
-        issueOperationsOf3dChildren(zTranslatedNodes, kNegativeZChildren, renderer, handler);
+            // for 3d root, draw children with negative z values
+            int shadowRestoreTo = issueOperationsOfNegZChildren(zTranslatedNodes, renderer, handler);
 
-        DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
-        const int saveCountOffset = renderer.getSaveCount() - 1;
-        const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
-        for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
-            DisplayListOp *op = mDisplayListData->displayListOps[i];
+            DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+            const int saveCountOffset = renderer.getSaveCount() - 1;
+            const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
+            for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
+                DisplayListOp *op = mDisplayListData->displayListOps[i];
 
 #if DEBUG_DISPLAY_LIST
-            op->output(level + 1);
+                op->output(level + 1);
 #endif
-            logBuffer.writeCommand(level, op->name());
-            handler(op, saveCountOffset, properties().getClipToBounds());
+                logBuffer.writeCommand(level, op->name());
+                handler(op, saveCountOffset, properties().getClipToBounds());
 
-            if (CC_UNLIKELY(i == projectionReceiveIndex && mProjectedNodes.size() > 0)) {
-                issueOperationsOfProjectedChildren(renderer, handler);
+                if (CC_UNLIKELY(i == projectionReceiveIndex && mProjectedNodes.size() > 0)) {
+                    issueOperationsOfProjectedChildren(renderer, handler);
+                }
             }
-        }
 
-        // for 3d root, draw children with positive z values
-        issueOperationsOf3dChildren(zTranslatedNodes, kPositiveZChildren, renderer, handler);
+            // for 3d root, draw children with positive z values
+            issueOperationsOfPosZChildren(shadowRestoreTo, zTranslatedNodes, renderer, handler);
+        }
     }
 
     DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1a5377b..7d42b59 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -20,18 +20,11 @@
     #define LOG_TAG "OpenGLRenderer"
 #endif
 
-#include <set>
-#include <vector>
-
 #include <SkCamera.h>
 #include <SkMatrix.h>
 
-#include <private/hwui/DrawGlInfo.h>
-
-#include <utils/KeyedVector.h>
 #include <utils/LinearAllocator.h>
 #include <utils/RefBase.h>
-#include <utils/SortedVector.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
@@ -39,6 +32,8 @@
 
 #include <androidfw/ResourceTypes.h>
 
+#include "AnimatorManager.h"
+#include "DamageAccumulator.h"
 #include "Debug.h"
 #include "Matrix.h"
 #include "DeferredDisplayList.h"
@@ -54,7 +49,6 @@
 namespace android {
 namespace uirenderer {
 
-class DeferredDisplayList;
 class DisplayListOp;
 class DisplayListRenderer;
 class OpenGLRenderer;
@@ -66,7 +60,7 @@
 class SaveLayerOp;
 class SaveOp;
 class RestoreToCountOp;
-class DrawDisplayListOp;
+class DrawRenderNodeOp;
 
 /**
  * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
@@ -112,11 +106,8 @@
 
     void computeOrdering();
 
-    void deferNodeTree(DeferStateStruct& deferStruct);
-    void deferNodeInParent(DeferStateStruct& deferStruct, const int level);
-
-    void replayNodeTree(ReplayStateStruct& replayStruct);
-    void replayNodeInParent(ReplayStateStruct& replayStruct, const int level);
+    void defer(DeferStateStruct& deferStruct, const int level);
+    void replay(ReplayStateStruct& replayStruct, const int level);
 
     ANDROID_API void output(uint32_t level = 1);
     ANDROID_API int getDebugSize();
@@ -125,6 +116,10 @@
         return mDisplayListData && mDisplayListData->hasDrawOps;
     }
 
+    bool hasProjectionReceiver() const {
+        return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0;
+    }
+
     const char* getName() const {
         return mName.string();
     }
@@ -148,7 +143,7 @@
         mDirtyPropertyFields |= fields;
     }
 
-    const RenderProperties& properties() {
+    const RenderProperties& properties() const {
         return mProperties;
     }
 
@@ -175,22 +170,15 @@
     ANDROID_API virtual void prepareTree(TreeInfo& info);
 
     // UI thread only!
-    ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-        animator->onAttached(this);
-        mStagingAnimators.insert(animator);
-        mNeedsAnimatorsSync = true;
-    }
+    ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
 
-    // UI thread only!
-    ANDROID_API void removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-        mStagingAnimators.erase(animator);
-        mNeedsAnimatorsSync = true;
-    }
+protected:
+    virtual void damageSelf(TreeInfo& info);
 
 private:
-    typedef key_value_pair_t<float, DrawDisplayListOp*> ZDrawDisplayListOpPair;
+    typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
 
-    static size_t findNonNegativeIndex(const Vector<ZDrawDisplayListOpPair>& nodes) {
+    static size_t findNonNegativeIndex(const Vector<ZDrawRenderNodeOpPair>& nodes) {
         for (size_t i = 0; i < nodes.size(); i++) {
             if (nodes[i].key >= 0.0f) return i;
         }
@@ -204,21 +192,29 @@
 
     void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false);
 
-    void computeOrderingImpl(DrawDisplayListOp* opState,
+    void computeOrderingImpl(DrawRenderNodeOp* opState,
             const SkPath* outlineOfProjectionSurface,
-            Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
+            Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
             const mat4* transformFromProjectionSurface);
 
     template <class T>
     inline void setViewProperties(OpenGLRenderer& renderer, T& handler);
 
-    void buildZSortedChildList(Vector<ZDrawDisplayListOpPair>& zTranslatedNodes);
+    void buildZSortedChildList(Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes);
 
     template<class T>
     inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler);
 
     template <class T>
-    inline void issueOperationsOf3dChildren(const Vector<ZDrawDisplayListOpPair>& zTranslatedNodes,
+    inline int issueOperationsOfNegZChildren(
+            const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+            OpenGLRenderer& renderer, T& handler);
+    template <class T>
+    inline void issueOperationsOfPosZChildren(int shadowRestoreTo,
+            const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+            OpenGLRenderer& renderer, T& handler);
+    template <class T>
+    inline void issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
             ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler);
 
     template <class T>
@@ -226,7 +222,7 @@
 
     /**
      * Issue the RenderNode's operations into a handler, recursing for subtrees through
-     * DrawDisplayListOp's defer() or replay() methods
+     * DrawRenderNodeOp's defer() or replay() methods
      */
     template <class T>
     inline void issueOperations(OpenGLRenderer& renderer, T& handler);
@@ -246,9 +242,12 @@
     };
 
     void prepareTreeImpl(TreeInfo& info);
-    void pushStagingChanges(TreeInfo& info);
-    void evaluateAnimations(TreeInfo& info);
+    void pushStagingPropertiesChanges(TreeInfo& info);
+    void pushStagingDisplayListChanges(TreeInfo& info);
     void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
+    void applyLayerPropertiesToLayer(TreeInfo& info);
+    void prepareLayer(TreeInfo& info);
+    void pushLayerUpdate(TreeInfo& info);
 
     String8 mName;
 
@@ -260,16 +259,19 @@
     DisplayListData* mDisplayListData;
     DisplayListData* mStagingDisplayListData;
 
-    bool mNeedsAnimatorsSync;
-    std::set< sp<BaseRenderNodeAnimator> > mStagingAnimators;
-    std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+    friend class AnimatorManager;
+    AnimatorManager mAnimatorManager;
+
+    // Owned by RT. Lifecycle is managed by prepareTree(), with the exception
+    // being in ~RenderNode() which may happen on any thread.
+    Layer* mLayer;
 
     /**
      * Draw time state - these properties are only set and used during rendering
      */
 
     // for projection surfaces, contains a list of all children items
-    Vector<DrawDisplayListOp*> mProjectedNodes;
+    Vector<DrawRenderNodeOp*> mProjectedNodes;
 }; // class RenderNode
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 5f7d4e3..8848b2f 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -21,16 +21,59 @@
 #include <utils/Trace.h>
 
 #include <SkCanvas.h>
+#include <SkColorFilter.h>
 #include <SkMatrix.h>
 #include <SkPath.h>
 #include <SkPathOps.h>
 
 #include "Matrix.h"
+#include "OpenGLRenderer.h"
 #include "utils/MathUtils.h"
 
 namespace android {
 namespace uirenderer {
 
+LayerProperties::LayerProperties()
+        : mType(kLayerTypeNone)
+        , mColorFilter(NULL) {
+    reset();
+}
+
+LayerProperties::~LayerProperties() {
+    setType(kLayerTypeNone);
+}
+
+void LayerProperties::reset() {
+    mOpaque = false;
+    setFromPaint(NULL);
+}
+
+bool LayerProperties::setColorFilter(SkColorFilter* filter) {
+   if (mColorFilter == filter) return false;
+   SkRefCnt_SafeAssign(mColorFilter, filter);
+   return true;
+}
+
+bool LayerProperties::setFromPaint(const SkPaint* paint) {
+    bool changed = false;
+    SkXfermode::Mode mode;
+    int alpha;
+    OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
+    changed |= setAlpha(static_cast<uint8_t>(alpha));
+    changed |= setXferMode(mode);
+    changed |= setColorFilter(paint ? paint->getColorFilter() : NULL);
+    return changed;
+}
+
+LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
+    setType(other.type());
+    setOpaque(other.opaque());
+    setAlpha(other.alpha());
+    setXferMode(other.xferMode());
+    setColorFilter(other.colorFilter());
+    return *this;
+}
+
 RenderProperties::PrimitiveFields::PrimitiveFields()
         : mClipToBounds(true)
         , mProjectBackwards(false)
@@ -45,8 +88,7 @@
         , mLeft(0), mTop(0), mRight(0), mBottom(0)
         , mWidth(0), mHeight(0)
         , mPivotExplicitlySet(false)
-        , mMatrixOrPivotDirty(false)
-        , mCaching(false) {
+        , mMatrixOrPivotDirty(false) {
 }
 
 RenderProperties::ComputedFields::ComputedFields()
@@ -73,6 +115,7 @@
         setStaticMatrix(other.getStaticMatrix());
         setAnimationMatrix(other.getAnimationMatrix());
         setCameraDistance(other.getCameraDistance());
+        mLayerProperties = other.layerProperties();
 
         // Force recalculation of the matrix, since other's dirty bit may be clear
         mPrimitiveFields.mMatrixOrPivotDirty = true;
@@ -103,9 +146,9 @@
         }
     }
 
-    bool clipToBoundsNeeded = mPrimitiveFields.mCaching ? false : mPrimitiveFields.mClipToBounds;
+    bool clipToBoundsNeeded = layerProperties().type() != kLayerTypeNone ? false : mPrimitiveFields.mClipToBounds;
     if (mPrimitiveFields.mAlpha < 1) {
-        if (mPrimitiveFields.mCaching) {
+        if (layerProperties().type() != kLayerTypeNone) {
             ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
         } else if (!mPrimitiveFields.mHasOverlappingRendering) {
             ALOGD("%*sScaleAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index c0e3ce7..227d56e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -21,6 +21,7 @@
 #include <vector>
 #include <cutils/compiler.h>
 #include <androidfw/ResourceTypes.h>
+#include <utils/Log.h>
 
 #include <SkCamera.h>
 #include <SkMatrix.h>
@@ -30,8 +31,10 @@
 #include "Rect.h"
 #include "RevealClip.h"
 #include "Outline.h"
+#include "utils/MathUtils.h"
 
 class SkBitmap;
+class SkColorFilter;
 class SkPaint;
 
 namespace android {
@@ -39,40 +42,125 @@
 
 class Matrix4;
 class RenderNode;
+class RenderProperties;
+
+// 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)
+
+// Keep in sync with View.java:LAYER_TYPE_*
+enum LayerType {
+    kLayerTypeNone = 0,
+    // Although we cannot build the software layer directly (must be done at
+    // record time), this information is used when applying alpha.
+    kLayerTypeSoftware = 1,
+    kLayerTypeRenderLayer = 2,
+    // TODO: LayerTypeSurfaceTexture? Maybe?
+};
+
+class ANDROID_API LayerProperties {
+public:
+    bool setType(LayerType type) {
+        if (RP_SET(mType, type)) {
+            reset();
+            return true;
+        }
+        return false;
+    }
+
+    LayerType type() const {
+        return mType;
+    }
+
+    bool setOpaque(bool opaque) {
+        return RP_SET(mOpaque, opaque);
+    }
+
+    bool opaque() const {
+        return mOpaque;
+    }
+
+    bool setAlpha(uint8_t alpha) {
+        return RP_SET(mAlpha, alpha);
+    }
+
+    uint8_t alpha() const {
+        return mAlpha;
+    }
+
+    bool setXferMode(SkXfermode::Mode mode) {
+        return RP_SET(mMode, mode);
+    }
+
+    SkXfermode::Mode xferMode() const {
+        return mMode;
+    }
+
+    bool setColorFilter(SkColorFilter* filter);
+
+    SkColorFilter* colorFilter() const {
+        return mColorFilter;
+    }
+
+    // Sets alpha, xfermode, and colorfilter from an SkPaint
+    // paint may be NULL, in which case defaults will be set
+    bool setFromPaint(const SkPaint* paint);
+
+    bool needsBlending() const {
+        return !opaque() || alpha() < 255;
+    }
+
+    LayerProperties& operator=(const LayerProperties& other);
+
+private:
+    LayerProperties();
+    ~LayerProperties();
+    void reset();
+
+    friend class RenderProperties;
+
+    LayerType mType;
+    // Whether or not that Layer's content is opaque, doesn't include alpha
+    bool mOpaque;
+    uint8_t mAlpha;
+    SkXfermode::Mode mMode;
+    SkColorFilter* mColorFilter;
+};
 
 /*
  * Data structure that holds the properties for a RenderNode
  */
-class RenderProperties {
+class ANDROID_API RenderProperties {
 public:
     RenderProperties();
     virtual ~RenderProperties();
 
     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 +168,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 +230,8 @@
     }
 
     // Animation helper
-    void setX(float value) {
-        setTranslationX(value - getLeft());
+    bool setX(float value) {
+        return setTranslationX(value - getLeft());
     }
 
     // Animation helper
@@ -163,8 +240,8 @@
     }
 
     // Animation helper
-    void setY(float value) {
-        setTranslationY(value - getTop());
+    bool setY(float value) {
+        return setTranslationY(value - getTop());
     }
 
     // Animation helper
@@ -173,87 +250,82 @@
     }
 
     // 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) {
+        LOG_ALWAYS_FATAL_IF(scaleX > 1000000, "invalid scaleX %e", 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) {
+        LOG_ALWAYS_FATAL_IF(scaleY > 1000000, "invalid scaleY %e", 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 +336,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 +350,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 +428,27 @@
             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;
         }
-    }
-
-    void setCaching(bool caching) {
-        mPrimitiveFields.mCaching = caching;
+        return false;
     }
 
     int getWidth() const {
@@ -409,10 +477,6 @@
         return mComputedFields.mTransformMatrix;
     }
 
-    bool getCaching() const {
-        return mPrimitiveFields.mCaching;
-    }
-
     bool getClipToBounds() const {
         return mPrimitiveFields.mClipToBounds;
     }
@@ -435,7 +499,7 @@
 
     void debugOutputProperties(const int level) const;
 
-    ANDROID_API void updateMatrix();
+    void updateMatrix();
 
     bool hasClippingPath() const {
         return mPrimitiveFields.mRevealClip.willClip();
@@ -458,6 +522,23 @@
         return mPrimitiveFields.mRevealClip;
     }
 
+    const LayerProperties& layerProperties() const {
+        return mLayerProperties;
+    }
+
+    LayerProperties& mutateLayerProperties() {
+        return mLayerProperties;
+    }
+
+    // Returns true if damage calculations should be clipped to bounds
+    // TODO: Figure out something better for getZ(), as children should still be
+    // clipped to this RP's bounds. But as we will damage -INT_MAX to INT_MAX
+    // for this RP's getZ() anyway, this can be optimized when we have a
+    // Z damage estimate instead of INT_MAX
+    bool getClipDamageToBounds() const {
+        return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty());
+    }
+
 private:
 
     // Rendering properties
@@ -480,11 +561,11 @@
         int mWidth, mHeight;
         bool mPivotExplicitlySet;
         bool mMatrixOrPivotDirty;
-        bool mCaching;
     } mPrimitiveFields;
 
     SkMatrix* mStaticMatrix;
     SkMatrix* mAnimationMatrix;
+    LayerProperties mLayerProperties;
 
     /**
      * These fields are all generated from other properties and are not set directly.
diff --git a/libs/hwui/RenderState.cpp b/libs/hwui/RenderState.cpp
new file mode 100644
index 0000000..97f379d
--- /dev/null
+++ b/libs/hwui/RenderState.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 "RenderState.h"
+
+namespace android {
+namespace uirenderer {
+
+RenderState::RenderState()
+        : mCaches(NULL)
+        , mViewportWidth(0)
+        , mViewportHeight(0)
+        , mFramebuffer(0) {
+}
+
+RenderState::~RenderState() {
+}
+
+void RenderState::onGLContextCreated() {
+    // This is delayed because the first access of Caches makes GL calls
+    mCaches = &Caches::getInstance();
+    mCaches->init();
+}
+
+void RenderState::setViewport(GLsizei width, GLsizei height) {
+    mViewportWidth = width;
+    mViewportHeight = height;
+    glViewport(0, 0, mViewportWidth, mViewportHeight);
+}
+
+
+void RenderState::getViewport(GLsizei* outWidth, GLsizei* outHeight) {
+    *outWidth = mViewportWidth;
+    *outHeight = mViewportHeight;
+}
+
+void RenderState::bindFramebuffer(GLuint fbo) {
+    if (mFramebuffer != fbo) {
+        mFramebuffer = fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+    }
+}
+
+void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
+    interruptForFunctorInvoke();
+    (*functor)(mode, info);
+    resumeFromFunctorInvoke();
+}
+
+void RenderState::interruptForFunctorInvoke() {
+    if (mCaches->currentProgram) {
+        if (mCaches->currentProgram->isInUse()) {
+            mCaches->currentProgram->remove();
+            mCaches->currentProgram = NULL;
+        }
+    }
+    mCaches->resetActiveTexture();
+    mCaches->unbindMeshBuffer();
+    mCaches->unbindIndicesBuffer();
+    mCaches->resetVertexPointers();
+    mCaches->disableTexCoordsVertexArray();
+    debugOverdraw(false, false);
+}
+
+void RenderState::resumeFromFunctorInvoke() {
+    glViewport(0, 0, mViewportWidth, mViewportHeight);
+    glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+    debugOverdraw(false, false);
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+
+    mCaches->scissorEnabled = glIsEnabled(GL_SCISSOR_TEST);
+    mCaches->enableScissor();
+    mCaches->resetScissor();
+
+    mCaches->activeTexture(0);
+    mCaches->resetBoundTextures();
+
+    mCaches->blend = true;
+    glEnable(GL_BLEND);
+    glBlendFunc(mCaches->lastSrcMode, mCaches->lastDstMode);
+    glBlendEquation(GL_FUNC_ADD);
+}
+
+void RenderState::debugOverdraw(bool enable, bool clear) {
+    if (mCaches->debugOverdraw && mFramebuffer == 0) {
+        if (clear) {
+            mCaches->disableScissor();
+            mCaches->stencil.clear();
+        }
+        if (enable) {
+            mCaches->stencil.enableDebugWrite();
+        } else {
+            mCaches->stencil.disable();
+        }
+    }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/RenderState.h b/libs/hwui/RenderState.h
new file mode 100644
index 0000000..f7116e2
--- /dev/null
+++ b/libs/hwui/RenderState.h
@@ -0,0 +1,70 @@
+/*
+ * 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 RENDERSTATE_H
+#define RENDERSTATE_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <private/hwui/DrawGlInfo.h>
+
+#include "Caches.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace renderthread {
+class RenderThread;
+}
+
+// TODO: Replace Cache's GL state tracking with this. For now it's more a thin
+// wrapper of Caches for users to migrate to.
+class RenderState {
+    PREVENT_COPY_AND_ASSIGN(RenderState);
+public:
+    void onGLContextCreated();
+
+    void setViewport(GLsizei width, GLsizei height);
+    void getViewport(GLsizei* outWidth, GLsizei* outHeight);
+
+    void bindFramebuffer(GLuint fbo);
+    GLint getFramebuffer() { return mFramebuffer; }
+
+    void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
+
+    void debugOverdraw(bool enable, bool clear);
+
+private:
+    friend class renderthread::RenderThread;
+
+    void interruptForFunctorInvoke();
+    void resumeFromFunctorInvoke();
+
+    RenderState();
+    ~RenderState();
+
+    Caches* mCaches;
+
+    GLsizei mViewportWidth;
+    GLsizei mViewportHeight;
+    GLuint mFramebuffer;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* RENDERSTATE_H */
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
index 23cab0e..2ec99c9 100644
--- a/libs/hwui/Renderer.h
+++ b/libs/hwui/Renderer.h
@@ -129,21 +129,6 @@
      */
     virtual void finish() = 0;
 
-    /**
-     * This method must be invoked before handing control over to a draw functor.
-     * See callDrawGLFunction() for instance.
-     *
-     * This command must not be recorded inside display lists.
-     */
-    virtual void interrupt() = 0;
-
-    /**
-     * This method must be invoked after getting control back from a draw functor.
-     *
-     * This command must not be recorded inside display lists.
-     */
-    virtual void resume() = 0;
-
 // ----------------------------------------------------------------------------
 // Canvas state operations
 // ----------------------------------------------------------------------------
@@ -170,8 +155,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 +178,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,
@@ -233,7 +218,7 @@
 // Canvas draw operations - special
 // ----------------------------------------------------------------------------
     virtual status_t drawLayer(Layer* layer, float x, float y) = 0;
-    virtual status_t drawDisplayList(RenderNode* displayList, Rect& dirty,
+    virtual status_t drawRenderNode(RenderNode* renderNode, Rect& dirty,
             int32_t replayFlags) = 0;
 
     // TODO: rename for consistency
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 37d5347..08b54ff 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -39,6 +39,8 @@
 
 TessellationCache::Description::Description()
         : type(kNone)
+        , scaleX(1.0f)
+        , scaleY(1.0f)
         , aa(false)
         , cap(SkPaint::kDefault_Cap)
         , style(SkPaint::kFill_Style)
@@ -46,21 +48,13 @@
     memset(&shape, 0, sizeof(Shape));
 }
 
-TessellationCache::Description::Description(Type type)
+TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint)
         : type(type)
-        , aa(false)
-        , cap(SkPaint::kDefault_Cap)
-        , style(SkPaint::kFill_Style)
-        , strokeWidth(1.0f) {
-    memset(&shape, 0, sizeof(Shape));
-}
-
-TessellationCache::Description::Description(Type type, const SkPaint* paint)
-        : type(type)
-        , aa(paint->isAntiAlias())
-        , cap(paint->getStrokeCap())
-        , style(paint->getStyle())
-        , strokeWidth(paint->getStrokeWidth()) {
+        , aa(paint.isAntiAlias())
+        , cap(paint.getStrokeCap())
+        , style(paint.getStyle())
+        , strokeWidth(paint.getStrokeWidth()) {
+    PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
     memset(&shape, 0, sizeof(Shape));
 }
 
@@ -70,10 +64,20 @@
     hash = JenkinsHashMix(hash, cap);
     hash = JenkinsHashMix(hash, style);
     hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+    hash = JenkinsHashMix(hash, android::hash_type(scaleX));
+    hash = JenkinsHashMix(hash, android::hash_type(scaleY));
     hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
     return JenkinsHashWhiten(hash);
 }
 
+void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const {
+    matrix->loadScale(scaleX, scaleY, 1.0f);
+    paint->setAntiAlias(aa);
+    paint->setStrokeCap(cap);
+    paint->setStyle(style);
+    paint->setStrokeWidth(strokeWidth);
+}
+
 TessellationCache::ShadowDescription::ShadowDescription()
         : nodeKey(NULL) {
     memset(&matrixData, 0, 16 * sizeof(float));
@@ -96,20 +100,15 @@
 
 class TessellationCache::TessellationTask : public Task<VertexBuffer*> {
 public:
-    TessellationTask(Tessellator tessellator, const Description& description,
-                const SkPaint* paint)
+    TessellationTask(Tessellator tessellator, const Description& description)
         : tessellator(tessellator)
-        , description(description)
-        , paint(*paint) {
+        , description(description) {
     }
 
     ~TessellationTask() {}
 
     Tessellator tessellator;
     Description description;
-
-    //copied, since input paint may not be immutable
-    const SkPaint paint;
 };
 
 class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> {
@@ -121,7 +120,7 @@
     virtual void onProcess(const sp<Task<VertexBuffer*> >& task) {
         TessellationTask* t = static_cast<TessellationTask*>(task.get());
         ATRACE_NAME("shape tessellation");
-        VertexBuffer* buffer = t->tessellator(t->description, t->paint);
+        VertexBuffer* buffer = t->tessellator(t->description);
         t->setResult(buffer);
     }
 };
@@ -354,12 +353,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();
 }
 
@@ -417,21 +415,12 @@
 // Tessellation precaching
 ///////////////////////////////////////////////////////////////////////////////
 
-static VertexBuffer* tessellatePath(const SkPath& path, const SkPaint* paint,
-        float scaleX, float scaleY) {
-    VertexBuffer* buffer = new VertexBuffer();
-    Matrix4 matrix;
-    matrix.loadScale(scaleX, scaleY, 1);
-    PathTessellator::tessellatePath(path, paint, matrix, *buffer);
-    return buffer;
-}
-
 TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
-        const Description& entry, Tessellator tessellator, const SkPaint* paint) {
+        const Description& entry, Tessellator tessellator) {
     Buffer* buffer = mCache.get(entry);
     if (!buffer) {
         // not cached, enqueue a task to fill the buffer
-        sp<TessellationTask> task = new TessellationTask(tessellator, entry, paint);
+        sp<TessellationTask> task = new TessellationTask(tessellator, entry);
         buffer = new Buffer(task);
 
         if (mProcessor == NULL) {
@@ -443,43 +432,49 @@
     return buffer;
 }
 
+static VertexBuffer* tessellatePath(const TessellationCache::Description& description,
+        const SkPath& path) {
+    Matrix4 matrix;
+    SkPaint paint;
+    description.setupMatrixAndPaint(&matrix, &paint);
+    VertexBuffer* buffer = new VertexBuffer();
+    PathTessellator::tessellatePath(path, &paint, matrix, *buffer);
+    return buffer;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
-// Rounded rects
+// RoundRect
 ///////////////////////////////////////////////////////////////////////////////
 
-static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description,
-        const SkPaint& paint) {
-    SkRect rect = SkRect::MakeWH(description.shape.roundRect.mWidth,
-            description.shape.roundRect.mHeight);
-    float rx = description.shape.roundRect.mRx;
-    float ry = description.shape.roundRect.mRy;
-    if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) {
-        float outset = paint.getStrokeWidth() / 2;
+static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) {
+    SkRect rect = SkRect::MakeWH(description.shape.roundRect.width,
+            description.shape.roundRect.height);
+    float rx = description.shape.roundRect.rx;
+    float ry = description.shape.roundRect.ry;
+    if (description.style == SkPaint::kStrokeAndFill_Style) {
+        float outset = description.strokeWidth / 2;
         rect.outset(outset, outset);
         rx += outset;
         ry += outset;
     }
     SkPath path;
     path.addRoundRect(rect, rx, ry);
-    return tessellatePath(path, &paint,
-            description.shape.roundRect.mScaleX, description.shape.roundRect.mScaleY);
+    return tessellatePath(description, path);
 }
 
-TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(const Matrix4& transform,
-        float width, float height, float rx, float ry, const SkPaint* paint) {
-    Description entry(Description::kRoundRect, paint);
-    entry.shape.roundRect.mWidth = width;
-    entry.shape.roundRect.mHeight = height;
-    entry.shape.roundRect.mRx = rx;
-    entry.shape.roundRect.mRy = ry;
-    PathTessellator::extractTessellationScales(transform,
-            &entry.shape.roundRect.mScaleX, &entry.shape.roundRect.mScaleY);
-
-    return getOrCreateBuffer(entry, &tessellateRoundRect, paint);
+TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(
+        const Matrix4& transform, const SkPaint& paint,
+        float width, float height, float rx, float ry) {
+    Description entry(Description::kRoundRect, transform, paint);
+    entry.shape.roundRect.width = width;
+    entry.shape.roundRect.height = height;
+    entry.shape.roundRect.rx = rx;
+    entry.shape.roundRect.ry = ry;
+    return getOrCreateBuffer(entry, &tessellateRoundRect);
 }
-const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform,
-        float width, float height, float rx, float ry, const SkPaint* paint) {
-    return getRoundRectBuffer(transform, width, height, rx, ry, paint)->getVertexBuffer();
+const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint,
+        float width, float height, float rx, float ry) {
+    return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer();
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index d4ff943..688a699 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -50,30 +50,28 @@
         enum Type {
             kNone,
             kRoundRect,
-            kAmbientShadow,
-            kSpotShadow
         };
 
         Type type;
+        float scaleX;
+        float scaleY;
         bool aa;
         SkPaint::Cap cap;
         SkPaint::Style style;
         float strokeWidth;
         union Shape {
             struct RoundRect {
-                float mScaleX;
-                float mScaleY;
-                float mWidth;
-                float mHeight;
-                float mRx;
-                float mRy;
+                float width;
+                float height;
+                float rx;
+                float ry;
             } roundRect;
         } shape;
 
         Description();
-        Description(Type type);
-        Description(Type type, const SkPaint* paint);
+        Description(Type type, const Matrix4& transform, const SkPaint& paint);
         hash_t hash() const;
+        void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const;
     };
 
     struct ShadowDescription {
@@ -123,12 +121,12 @@
 
     // TODO: precache/get for Oval, Lines, Points, etc.
 
-    void precacheRoundRect(const Matrix4& transform,
-            float width, float height, float rx, float ry, const SkPaint* paint) {
-        getRoundRectBuffer(transform, width, height, rx, ry, paint);
+    void precacheRoundRect(const Matrix4& transform, const SkPaint& paint,
+            float width, float height, float rx, float ry) {
+        getRoundRectBuffer(transform, paint, width, height, rx, ry);
     }
-    const VertexBuffer* getRoundRect(const Matrix4& transform,
-            float width, float height, float rx, float ry, const SkPaint* paint);
+    const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint,
+            float width, float height, float rx, float ry);
 
     void precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
             bool opaque, const SkPath* casterPerimeter,
@@ -146,14 +144,14 @@
     class TessellationTask;
     class TessellationProcessor;
 
+    typedef VertexBuffer* (*Tessellator)(const Description&);
 
-    typedef VertexBuffer* (*Tessellator)(const Description&, const SkPaint&);
+    Buffer* getRectBuffer(const Matrix4& transform, const SkPaint& paint,
+            float width, float height);
+    Buffer* getRoundRectBuffer(const Matrix4& transform, const SkPaint& paint,
+            float width, float height, float rx, float ry);
 
-    Buffer* getRoundRectBuffer(const Matrix4& transform,
-            float width, float height, float rx, float ry, const SkPaint* paint);
-
-    Buffer* getOrCreateBuffer(const Description& entry,
-            Tessellator tessellator, const SkPaint* paint);
+    Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator);
 
     uint32_t mSize;
     uint32_t mMaxSize;
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 60b4b96..9212d0a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -288,19 +288,19 @@
     switch (bitmap->config()) {
     case SkBitmap::kA8_Config:
         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-        uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(),
+        uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
                 texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
         texture->blend = true;
         break;
     case SkBitmap::kRGB_565_Config:
         glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
-        uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(),
+        uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
                 texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
         texture->blend = false;
         break;
     case SkBitmap::kARGB_8888_Config:
         glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
-        uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(),
+        uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
                 texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
         // Do this after calling getPixels() to make sure Skia's deferred
         // decoding happened
@@ -333,34 +333,55 @@
 void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap,
         uint32_t width, uint32_t height) {
     SkBitmap rgbaBitmap;
-    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType());
-    rgbaBitmap.allocPixels();
+    rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType()));
     rgbaBitmap.eraseColor(0);
 
     SkCanvas canvas(rgbaBitmap);
     canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
 
-    uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), width, height,
-            GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
+    uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), rgbaBitmap.bytesPerPixel(),
+            width, height, GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
 }
 
-void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride,
+void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
         GLsizei width, GLsizei height, GLenum type, const GLvoid * data) {
-    // TODO: With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
-    //       if the stride doesn't match the width
     const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength();
-    if (useStride) {
-        glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
-    }
+    if ((stride == width) || useStride) {
+        if (useStride) {
+            glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
+        }
 
-    if (resize) {
-        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+        if (resize) {
+            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+        } else {
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+        }
+
+        if (useStride) {
+            glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        }
     } else {
-        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
-    }
+        //  With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
+        //  if the stride doesn't match the width
 
-    if (useStride) {
-        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
+        if (!temp) return;
+
+        uint8_t * pDst = (uint8_t *)temp;
+        uint8_t * pSrc = (uint8_t *)data;
+        for (GLsizei i = 0; i < height; i++) {
+            memcpy(pDst, pSrc, width * bpp);
+            pDst += width * bpp;
+            pSrc += stride * bpp;
+        }
+
+        if (resize) {
+            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+        } else {
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
+        }
+
+        free(temp);
     }
 }
 
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index e5b5c1a..61db5b0 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -142,7 +142,7 @@
     void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate = false);
 
     void uploadLoFiTexture(bool resize, const SkBitmap* bitmap, uint32_t width, uint32_t height);
-    void uploadToTexture(bool resize, GLenum format, GLsizei stride,
+    void uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
             GLsizei width, GLsizei height, GLenum type, const GLvoid * data);
 
     void init();
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 8355f83..083100e 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,13 +16,20 @@
 #ifndef TREEINFO_H
 #define TREEINFO_H
 
+#include <string>
+
 #include <utils/Timers.h>
 
+#include "DamageAccumulator.h"
+#include "utils/Macros.h"
+
 namespace android {
 namespace uirenderer {
 
 class BaseRenderNodeAnimator;
 class AnimationListener;
+class OpenGLRenderer;
+class RenderState;
 
 class AnimationHook {
 public:
@@ -31,21 +38,71 @@
     ~AnimationHook() {}
 };
 
-struct TreeInfo {
-    // The defaults here should be safe for everyone but DrawFrameTask to use as-is.
-    TreeInfo()
-        : frameTimeMs(0)
+class ErrorHandler {
+public:
+    virtual void onError(const std::string& message) = 0;
+protected:
+    ~ErrorHandler() {}
+};
+
+// 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, RenderState& renderState)
+        : mode(mode)
+        , frameTimeMs(0)
         , animationHook(NULL)
-        , prepareTextures(false)
-        , performStagingPush(true)
-        , evaluateAnimations(false)
+        , prepareTextures(mode == MODE_FULL)
+        , damageAccumulator(NullDamageAccumulator::instance())
+        , renderState(renderState)
+        , renderer(NULL)
+        , errorHandler(NULL)
     {}
 
+    explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
+        : mode(mode)
+        , frameTimeMs(clone.frameTimeMs)
+        , animationHook(clone.animationHook)
+        , prepareTextures(mode == MODE_FULL)
+        , damageAccumulator(clone.damageAccumulator)
+        , renderState(clone.renderState)
+        , renderer(clone.renderer)
+        , errorHandler(clone.errorHandler)
+    {}
+
+    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;
+    RenderState& renderState;
+    // The renderer that will be drawing the next frame. Use this to push any
+    // layer updates or similar. May be NULL.
+    OpenGLRenderer* renderer;
+    ErrorHandler* errorHandler;
 
     struct Out {
         Out()
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9ebee1d..9c3cf44 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -18,332 +18,31 @@
 
 #include "CanvasContext.h"
 
-#include <cutils/properties.h>
 #include <private/hwui/DrawGlInfo.h>
 #include <strings.h>
 
+#include "EglManager.h"
 #include "RenderThread.h"
 #include "../Caches.h"
 #include "../DeferredLayerUpdater.h"
+#include "../RenderState.h"
 #include "../LayerRenderer.h"
 #include "../OpenGLRenderer.h"
 #include "../Stencil.h"
 
-#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
-#define GLES_VERSION 2
-
-// Android-specific addition that is used to show when frames began in systrace
-EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
 
-#define ERROR_CASE(x) case x: return #x;
-static const char* egl_error_str(EGLint error) {
-    switch (error) {
-        ERROR_CASE(EGL_SUCCESS)
-        ERROR_CASE(EGL_NOT_INITIALIZED)
-        ERROR_CASE(EGL_BAD_ACCESS)
-        ERROR_CASE(EGL_BAD_ALLOC)
-        ERROR_CASE(EGL_BAD_ATTRIBUTE)
-        ERROR_CASE(EGL_BAD_CONFIG)
-        ERROR_CASE(EGL_BAD_CONTEXT)
-        ERROR_CASE(EGL_BAD_CURRENT_SURFACE)
-        ERROR_CASE(EGL_BAD_DISPLAY)
-        ERROR_CASE(EGL_BAD_MATCH)
-        ERROR_CASE(EGL_BAD_NATIVE_PIXMAP)
-        ERROR_CASE(EGL_BAD_NATIVE_WINDOW)
-        ERROR_CASE(EGL_BAD_PARAMETER)
-        ERROR_CASE(EGL_BAD_SURFACE)
-        ERROR_CASE(EGL_CONTEXT_LOST)
-    default:
-        return "Unknown error";
-    }
-}
-static const char* egl_error_str() {
-    return egl_error_str(eglGetError());
-}
-
-static bool load_dirty_regions_property() {
-    char buf[PROPERTY_VALUE_MAX];
-    int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true");
-    return !strncasecmp("true", buf, len);
-}
-
-// This class contains the shared global EGL objects, such as EGLDisplay
-// and EGLConfig, which are re-used by CanvasContext
-class GlobalContext {
-public:
-    static GlobalContext* get();
-
-    // Returns true on success, false on failure
-    void initialize();
-
-    bool hasContext();
-
-    void usePBufferSurface();
-    EGLSurface createSurface(EGLNativeWindowType window);
-    void destroySurface(EGLSurface surface);
-
-    void destroy();
-
-    bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
-    // Returns true if the current surface changed, false if it was already current
-    bool makeCurrent(EGLSurface surface);
-    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
-    void swapBuffers(EGLSurface surface);
-
-    bool enableDirtyRegions(EGLSurface surface);
-
-    void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
-
-private:
-    GlobalContext();
-    // GlobalContext is never destroyed, method is purposely not implemented
-    ~GlobalContext();
-
-    void loadConfig();
-    void createContext();
-    void initAtlas();
-
-    static GlobalContext* sContext;
-
-    EGLDisplay mEglDisplay;
-    EGLConfig mEglConfig;
-    EGLContext mEglContext;
-    EGLSurface mPBufferSurface;
-
-    const bool mRequestDirtyRegions;
-    bool mCanSetDirtyRegions;
-
-    EGLSurface mCurrentSurface;
-
-    sp<GraphicBuffer> mAtlasBuffer;
-    int64_t* mAtlasMap;
-    size_t mAtlasMapSize;
-};
-
-GlobalContext* GlobalContext::sContext = 0;
-
-GlobalContext* GlobalContext::get() {
-    if (!sContext) {
-        sContext = new GlobalContext();
-    }
-    return sContext;
-}
-
-GlobalContext::GlobalContext()
-        : mEglDisplay(EGL_NO_DISPLAY)
-        , mEglConfig(0)
-        , mEglContext(EGL_NO_CONTEXT)
-        , mPBufferSurface(EGL_NO_SURFACE)
-        , mRequestDirtyRegions(load_dirty_regions_property())
-        , mCurrentSurface(EGL_NO_SURFACE)
-        , mAtlasMap(NULL)
-        , mAtlasMapSize(0) {
-    mCanSetDirtyRegions = mRequestDirtyRegions;
-    ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false");
-}
-
-void GlobalContext::initialize() {
-    if (hasContext()) return;
-
-    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
-            "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
-
-    EGLint major, minor;
-    LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
-            "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
-
-    ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
-
-    loadConfig();
-    createContext();
-    usePBufferSurface();
-    Caches::getInstance().init();
-    initAtlas();
-}
-
-bool GlobalContext::hasContext() {
-    return mEglDisplay != EGL_NO_DISPLAY;
-}
-
-void GlobalContext::loadConfig() {
-    EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
-    EGLint attribs[] = {
-            EGL_RENDERABLE_TYPE, 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, Stencil::getStencilSize(),
-            EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
-            EGL_NONE
-    };
-
-    EGLint num_configs = 1;
-    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
-            || num_configs != 1) {
-        // Failed to get a valid config
-        if (mCanSetDirtyRegions) {
-            ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
-            // Try again without dirty regions enabled
-            mCanSetDirtyRegions = false;
-            loadConfig();
-        } else {
-            LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
-        }
-    }
-}
-
-void GlobalContext::createContext() {
-    EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
-    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
-    LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
-        "Failed to create context, error = %s", egl_error_str());
-}
-
-void GlobalContext::setTextureAtlas(const sp<GraphicBuffer>& buffer,
-        int64_t* map, size_t mapSize) {
-
-    // Already initialized
-    if (mAtlasBuffer.get()) {
-        ALOGW("Multiple calls to setTextureAtlas!");
-        delete map;
-        return;
-    }
-
-    mAtlasBuffer = buffer;
-    mAtlasMap = map;
-    mAtlasMapSize = mapSize;
-
-    if (hasContext()) {
-        usePBufferSurface();
-        initAtlas();
-    }
-}
-
-void GlobalContext::initAtlas() {
-    if (mAtlasBuffer.get()) {
-        Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize);
-    }
-}
-
-void GlobalContext::usePBufferSurface() {
-    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
-            "usePBufferSurface() called on uninitialized GlobalContext!");
-
-    if (mPBufferSurface == EGL_NO_SURFACE) {
-        EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
-        mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
-    }
-    makeCurrent(mPBufferSurface);
-}
-
-EGLSurface GlobalContext::createSurface(EGLNativeWindowType window) {
-    initialize();
-    return eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL);
-}
-
-void GlobalContext::destroySurface(EGLSurface surface) {
-    if (isCurrent(surface)) {
-        makeCurrent(EGL_NO_SURFACE);
-    }
-    if (!eglDestroySurface(mEglDisplay, surface)) {
-        ALOGW("Failed to destroy surface %p, error=%s", (void*)surface, egl_error_str());
-    }
-}
-
-void GlobalContext::destroy() {
-    if (mEglDisplay == EGL_NO_DISPLAY) return;
-
-    usePBufferSurface();
-    if (Caches::hasInstance()) {
-        Caches::getInstance().terminate();
-    }
-
-    eglDestroyContext(mEglDisplay, mEglContext);
-    eglDestroySurface(mEglDisplay, mPBufferSurface);
-    eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-    eglTerminate(mEglDisplay);
-    eglReleaseThread();
-
-    mEglDisplay = EGL_NO_DISPLAY;
-    mEglContext = EGL_NO_CONTEXT;
-    mPBufferSurface = EGL_NO_SURFACE;
-    mCurrentSurface = EGL_NO_SURFACE;
-}
-
-bool GlobalContext::makeCurrent(EGLSurface surface) {
-    if (isCurrent(surface)) return false;
-
-    if (surface == EGL_NO_SURFACE) {
-        // If we are setting EGL_NO_SURFACE we don't care about any of the potential
-        // return errors, which would only happen if mEglDisplay had already been
-        // destroyed in which case the current context is already NO_CONTEXT
-        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-    } else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
-        LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
-                (void*)surface, egl_error_str());
-    }
-    mCurrentSurface = surface;
-    return true;
-}
-
-void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
-    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
-            "Tried to beginFrame on EGL_NO_SURFACE!");
-    makeCurrent(surface);
-    if (width) {
-        eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
-    }
-    if (height) {
-        eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
-    }
-    eglBeginFrame(mEglDisplay, surface);
-}
-
-void GlobalContext::swapBuffers(EGLSurface surface) {
-    eglSwapBuffers(mEglDisplay, surface);
-    EGLint err = eglGetError();
-    LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
-            "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
-}
-
-bool GlobalContext::enableDirtyRegions(EGLSurface surface) {
-    if (!mRequestDirtyRegions) return false;
-
-    if (mCanSetDirtyRegions) {
-        if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) {
-            ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
-                    (void*) surface, egl_error_str());
-            return false;
-        }
-        return true;
-    }
-    // Perhaps it is already enabled?
-    EGLint value;
-    if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) {
-        ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p",
-                (void*) surface, egl_error_str());
-        return false;
-    }
-    return value == EGL_BUFFER_PRESERVED;
-}
-
-CanvasContext::CanvasContext(bool translucent, RenderNode* rootRenderNode)
-        : mRenderThread(RenderThread::getInstance())
+CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode)
+        : mRenderThread(thread)
+        , mEglManager(thread.eglManager())
         , mEglSurface(EGL_NO_SURFACE)
         , mDirtyRegionsEnabled(false)
         , mOpaque(!translucent)
-        , mCanvas(0)
+        , mCanvas(NULL)
         , mHaveNewSurface(false)
         , mRootRenderNode(rootRenderNode) {
-    mGlobalContext = GlobalContext::get();
 }
 
 CanvasContext::~CanvasContext() {
@@ -363,19 +62,16 @@
     mNativeWindow = window;
 
     if (mEglSurface != EGL_NO_SURFACE) {
-        mGlobalContext->destroySurface(mEglSurface);
+        mEglManager.destroySurface(mEglSurface);
         mEglSurface = EGL_NO_SURFACE;
     }
 
     if (window) {
-        mEglSurface = mGlobalContext->createSurface(window);
-        LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
-                "Failed to create EGLSurface for window %p, eglErr = %s",
-                (void*) window, egl_error_str());
+        mEglSurface = mEglManager.createSurface(window);
     }
 
     if (mEglSurface != EGL_NO_SURFACE) {
-        mDirtyRegionsEnabled = mGlobalContext->enableDirtyRegions(mEglSurface);
+        mDirtyRegionsEnabled = mEglManager.enableDirtyRegions(mEglSurface);
         mHaveNewSurface = true;
         makeCurrent();
     } else {
@@ -384,7 +80,7 @@
 }
 
 void CanvasContext::swapBuffers() {
-    mGlobalContext->swapBuffers(mEglSurface);
+    mEglManager.swapBuffers(mEglSurface);
     mHaveNewSurface = false;
 }
 
@@ -397,7 +93,7 @@
 bool CanvasContext::initialize(ANativeWindow* window) {
     if (mCanvas) return false;
     setSurface(window);
-    mCanvas = new OpenGLRenderer();
+    mCanvas = new OpenGLRenderer(mRenderThread.renderState());
     mCanvas->initProperties();
     return true;
 }
@@ -424,11 +120,11 @@
 void CanvasContext::makeCurrent() {
     // TODO: Figure out why this workaround is needed, see b/13913604
     // In the meantime this matches the behavior of GLRenderer, so it is not a regression
-    mHaveNewSurface |= mGlobalContext->makeCurrent(mEglSurface);
+    mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
 }
 
-void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info) {
-    bool success = layerUpdater->apply(info);
+void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
+    bool success = layerUpdater->apply();
     LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
     if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
         mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
@@ -439,6 +135,8 @@
     mRenderThread.removeFrameCallback(this);
 
     info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
+    info.damageAccumulator = &mDamageAccumulator;
+    info.renderer = mCanvas;
     mRootRenderNode->prepareTree(info);
 
     int runningBehind = 0;
@@ -450,9 +148,7 @@
     info.out.canDrawThisFrame = !runningBehind;
 
     if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
-        if (info.out.hasFunctors) {
-            info.out.requiresUiRedraw = true;
-        } else if (!info.out.requiresUiRedraw) {
+        if (!info.out.requiresUiRedraw) {
             // If animationsNeedsRedraw is set don't bother posting for an RT anim
             // as we will just end up fighting the UI thread.
             mRenderThread.postFrameCallback(this);
@@ -465,33 +161,36 @@
     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!");
+            "drawRenderNode called on a context with no canvas or surface!");
 
     profiler().markPlaybackStart();
 
+    SkRect dirty;
+    mDamageAccumulator.finish(&dirty);
+
     EGLint width, height;
-    mGlobalContext->beginFrame(mEglSurface, &width, &height);
+    mEglManager.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);
     }
 
     Rect outBounds;
-    status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds);
+    status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
 
     profiler().draw(mCanvas);
 
@@ -516,40 +215,34 @@
 
     profiler().startFrame();
 
-    TreeInfo info;
-    info.evaluateAnimations = true;
-    info.performStagingPush = false;
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
     info.prepareTextures = false;
 
     prepareTree(info);
     if (info.out.canDrawThisFrame) {
-        draw(NULL);
+        draw();
     }
 }
 
-void CanvasContext::invokeFunctor(Functor* functor) {
+void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
     ATRACE_CALL();
     DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
-    if (mGlobalContext->hasContext()) {
-        requireGlContext();
+    if (thread.eglManager().hasEglContext()) {
+        thread.eglManager().requireGlContext();
         mode = DrawGlInfo::kModeProcess;
     }
-    (*functor)(mode, NULL);
 
-    if (mCanvas) {
-        mCanvas->resume();
-    }
+    thread.renderState().invokeFunctor(functor, mode, NULL);
 }
 
 bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
     requireGlContext();
-    TreeInfo info;
-    layer->apply(info);
-    return LayerRenderer::copyLayer(layer->backingLayer(), bitmap);
+    layer->apply();
+    return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
 }
 
 void CanvasContext::flushCaches(Caches::FlushMode flushMode) {
-    if (mGlobalContext->hasContext()) {
+    if (mEglManager.hasEglContext()) {
         requireGlContext();
         Caches::getInstance().flush(flushMode);
     }
@@ -562,25 +255,25 @@
 
 Layer* CanvasContext::createRenderLayer(int width, int height) {
     requireSurface();
-    return LayerRenderer::createRenderLayer(width, height);
+    return LayerRenderer::createRenderLayer(mRenderThread.renderState(), width, height);
 }
 
 Layer* CanvasContext::createTextureLayer() {
     requireSurface();
-    return LayerRenderer::createTextureLayer();
+    return LayerRenderer::createTextureLayer(mRenderThread.renderState());
 }
 
 void CanvasContext::requireGlContext() {
     if (mEglSurface != EGL_NO_SURFACE) {
         makeCurrent();
     } else {
-        mGlobalContext->usePBufferSurface();
+        mEglManager.usePBufferSurface();
     }
 }
 
-void CanvasContext::setTextureAtlas(const sp<GraphicBuffer>& buffer,
-        int64_t* map, size_t mapSize) {
-    GlobalContext::get()->setTextureAtlas(buffer, map, mapSize);
+void CanvasContext::setTextureAtlas(RenderThread& thread,
+        const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
+    thread.eglManager().setTextureAtlas(buffer, map, mapSize);
 }
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 00c5bf0..dbfb3d2 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"
@@ -40,13 +41,13 @@
 
 namespace renderthread {
 
-class GlobalContext;
+class EglManager;
 
 // This per-renderer class manages the bridge between the global EGL context
 // and the render surface.
 class CanvasContext : public IFrameCallback {
 public:
-    CanvasContext(bool translucent, RenderNode* rootRenderNode);
+    CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode);
     virtual ~CanvasContext();
 
     bool initialize(ANativeWindow* window);
@@ -55,9 +56,9 @@
     void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
     void setOpaque(bool opaque);
     void makeCurrent();
-    void processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info);
+    void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
     void prepareTree(TreeInfo& info);
-    void draw(Rect* dirty);
+    void draw();
     void destroyCanvasAndSurface();
 
     // IFrameCallback, Chroreographer-driven frame callback entry point
@@ -67,15 +68,15 @@
 
     void flushCaches(Caches::FlushMode flushMode);
 
-    void invokeFunctor(Functor* functor);
+    static void invokeFunctor(RenderThread& thread, Functor* functor);
 
     void runWithGlContext(RenderTask* task);
 
     Layer* createRenderLayer(int width, int height);
     Layer* createTextureLayer();
 
-    ANDROID_API static void setTextureAtlas(const sp<GraphicBuffer>& buffer,
-            int64_t* map, size_t mapSize);
+    ANDROID_API static void setTextureAtlas(RenderThread& thread,
+            const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
 
     void notifyFramePending();
 
@@ -90,8 +91,8 @@
 
     void requireGlContext();
 
-    GlobalContext* mGlobalContext;
     RenderThread& mRenderThread;
+    EglManager& mEglManager;
     sp<ANativeWindow> mNativeWindow;
     EGLSurface mEglSurface;
     bool mDirtyRegionsEnabled;
@@ -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..dd34e09 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, mRenderThread->renderState());
         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,37 +120,25 @@
     }
 }
 
-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);
+        mContext->processLayerUpdate(mLayers[i].get());
     }
     mLayers.clear();
-    if (info.out.hasAnimations) {
-        // TODO: Uh... crap?
-    }
     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;
         }
     }
     // If prepareTextures is false, we ran out of texture cache space
-    return !info.out.hasFunctors && info.prepareTextures;
+    return info.prepareTextures;
 }
 
 void DrawFrameTask::unblockUiThread() {
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d4129b6..243cc5d 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -41,7 +41,7 @@
 
 enum SyncResult {
     kSync_OK = 0,
-    kSync_UIRedrawRequired = 1 << 1,
+    kSync_UIRedrawRequired = 1 << 0,
 };
 
 /*
@@ -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/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
new file mode 100644
index 0000000..05ca34d
--- /dev/null
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -0,0 +1,288 @@
+/*
+ * 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 "EglContext"
+
+#include "EglManager.h"
+
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include "../RenderState.h"
+#include "RenderThread.h"
+
+#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
+#define GLES_VERSION 2
+
+// Android-specific addition that is used to show when frames began in systrace
+EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+#define ERROR_CASE(x) case x: return #x;
+static const char* egl_error_str(EGLint error) {
+    switch (error) {
+        ERROR_CASE(EGL_SUCCESS)
+        ERROR_CASE(EGL_NOT_INITIALIZED)
+        ERROR_CASE(EGL_BAD_ACCESS)
+        ERROR_CASE(EGL_BAD_ALLOC)
+        ERROR_CASE(EGL_BAD_ATTRIBUTE)
+        ERROR_CASE(EGL_BAD_CONFIG)
+        ERROR_CASE(EGL_BAD_CONTEXT)
+        ERROR_CASE(EGL_BAD_CURRENT_SURFACE)
+        ERROR_CASE(EGL_BAD_DISPLAY)
+        ERROR_CASE(EGL_BAD_MATCH)
+        ERROR_CASE(EGL_BAD_NATIVE_PIXMAP)
+        ERROR_CASE(EGL_BAD_NATIVE_WINDOW)
+        ERROR_CASE(EGL_BAD_PARAMETER)
+        ERROR_CASE(EGL_BAD_SURFACE)
+        ERROR_CASE(EGL_CONTEXT_LOST)
+    default:
+        return "Unknown error";
+    }
+}
+static const char* egl_error_str() {
+    return egl_error_str(eglGetError());
+}
+
+static bool load_dirty_regions_property() {
+    char buf[PROPERTY_VALUE_MAX];
+    int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true");
+    return !strncasecmp("true", buf, len);
+}
+
+EglManager::EglManager(RenderThread& thread)
+        : mRenderThread(thread)
+        , mEglDisplay(EGL_NO_DISPLAY)
+        , mEglConfig(0)
+        , mEglContext(EGL_NO_CONTEXT)
+        , mPBufferSurface(EGL_NO_SURFACE)
+        , mRequestDirtyRegions(load_dirty_regions_property())
+        , mCurrentSurface(EGL_NO_SURFACE)
+        , mAtlasMap(NULL)
+        , mAtlasMapSize(0) {
+    mCanSetDirtyRegions = mRequestDirtyRegions;
+    ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false");
+}
+
+void EglManager::initialize() {
+    if (hasEglContext()) return;
+
+    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+            "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
+
+    EGLint major, minor;
+    LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
+            "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
+
+    ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
+
+    loadConfig();
+    createContext();
+    usePBufferSurface();
+    mRenderThread.renderState().onGLContextCreated();
+    initAtlas();
+}
+
+bool EglManager::hasEglContext() {
+    return mEglDisplay != EGL_NO_DISPLAY;
+}
+
+void EglManager::requireGlContext() {
+    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, "No EGL context");
+
+    // We don't care *WHAT* surface is active, just that one is active to give
+    // us access to the GL context
+    if (mCurrentSurface == EGL_NO_SURFACE) {
+        usePBufferSurface();
+    }
+}
+
+void EglManager::loadConfig() {
+    EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+    EGLint attribs[] = {
+            EGL_RENDERABLE_TYPE, 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, Stencil::getStencilSize(),
+            EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
+            EGL_NONE
+    };
+
+    EGLint num_configs = 1;
+    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
+            || num_configs != 1) {
+        // Failed to get a valid config
+        if (mCanSetDirtyRegions) {
+            ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+            // Try again without dirty regions enabled
+            mCanSetDirtyRegions = false;
+            loadConfig();
+        } else {
+            LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
+        }
+    }
+}
+
+void EglManager::createContext() {
+    EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
+    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
+    LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
+        "Failed to create context, error = %s", egl_error_str());
+}
+
+void EglManager::setTextureAtlas(const sp<GraphicBuffer>& buffer,
+        int64_t* map, size_t mapSize) {
+
+    // Already initialized
+    if (mAtlasBuffer.get()) {
+        ALOGW("Multiple calls to setTextureAtlas!");
+        delete map;
+        return;
+    }
+
+    mAtlasBuffer = buffer;
+    mAtlasMap = map;
+    mAtlasMapSize = mapSize;
+
+    if (hasEglContext()) {
+        usePBufferSurface();
+        initAtlas();
+    }
+}
+
+void EglManager::initAtlas() {
+    if (mAtlasBuffer.get()) {
+        Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize);
+    }
+}
+
+void EglManager::usePBufferSurface() {
+    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+            "usePBufferSurface() called on uninitialized GlobalContext!");
+
+    if (mPBufferSurface == EGL_NO_SURFACE) {
+        EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
+        mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
+    }
+    makeCurrent(mPBufferSurface);
+}
+
+EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
+    initialize();
+    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL);
+    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
+            "Failed to create EGLSurface for window %p, eglErr = %s",
+            (void*) window, egl_error_str());
+    return surface;
+}
+
+void EglManager::destroySurface(EGLSurface surface) {
+    if (isCurrent(surface)) {
+        makeCurrent(EGL_NO_SURFACE);
+    }
+    if (!eglDestroySurface(mEglDisplay, surface)) {
+        ALOGW("Failed to destroy surface %p, error=%s", (void*)surface, egl_error_str());
+    }
+}
+
+void EglManager::destroy() {
+    if (mEglDisplay == EGL_NO_DISPLAY) return;
+
+    usePBufferSurface();
+    if (Caches::hasInstance()) {
+        Caches::getInstance().terminate();
+    }
+
+    eglDestroyContext(mEglDisplay, mEglContext);
+    eglDestroySurface(mEglDisplay, mPBufferSurface);
+    eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    eglTerminate(mEglDisplay);
+    eglReleaseThread();
+
+    mEglDisplay = EGL_NO_DISPLAY;
+    mEglContext = EGL_NO_CONTEXT;
+    mPBufferSurface = EGL_NO_SURFACE;
+    mCurrentSurface = EGL_NO_SURFACE;
+}
+
+bool EglManager::makeCurrent(EGLSurface surface) {
+    if (isCurrent(surface)) return false;
+
+    if (surface == EGL_NO_SURFACE) {
+        // If we are setting EGL_NO_SURFACE we don't care about any of the potential
+        // return errors, which would only happen if mEglDisplay had already been
+        // destroyed in which case the current context is already NO_CONTEXT
+        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    } else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
+        LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
+                (void*)surface, egl_error_str());
+    }
+    mCurrentSurface = surface;
+    return true;
+}
+
+void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
+    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
+            "Tried to beginFrame on EGL_NO_SURFACE!");
+    makeCurrent(surface);
+    if (width) {
+        eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
+    }
+    if (height) {
+        eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
+    }
+    eglBeginFrame(mEglDisplay, surface);
+}
+
+void EglManager::swapBuffers(EGLSurface surface) {
+    eglSwapBuffers(mEglDisplay, surface);
+    EGLint err = eglGetError();
+    LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
+            "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
+}
+
+bool EglManager::enableDirtyRegions(EGLSurface surface) {
+    if (!mRequestDirtyRegions) return false;
+
+    if (mCanSetDirtyRegions) {
+        if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) {
+            ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
+                    (void*) surface, egl_error_str());
+            return false;
+        }
+        return true;
+    }
+    // Perhaps it is already enabled?
+    EGLint value;
+    if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) {
+        ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p",
+                (void*) surface, egl_error_str());
+        return false;
+    }
+    return value == EGL_BUFFER_PRESERVED;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
new file mode 100644
index 0000000..a844cfc
--- /dev/null
+++ b/libs/hwui/renderthread/EglManager.h
@@ -0,0 +1,88 @@
+/*
+ * 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 EGLMANAGER_H
+#define EGLMANAGER_H
+
+#include <cutils/compiler.h>
+#include <EGL/egl.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class RenderThread;
+
+// This class contains the shared global EGL objects, such as EGLDisplay
+// and EGLConfig, which are re-used by CanvasContext
+class EglManager {
+public:
+    // Returns true on success, false on failure
+    void initialize();
+
+    bool hasEglContext();
+    void requireGlContext();
+
+    void usePBufferSurface();
+    EGLSurface createSurface(EGLNativeWindowType window);
+    void destroySurface(EGLSurface surface);
+
+    void destroy();
+
+    bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
+    // Returns true if the current surface changed, false if it was already current
+    bool makeCurrent(EGLSurface surface);
+    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
+    void swapBuffers(EGLSurface surface);
+
+    bool enableDirtyRegions(EGLSurface surface);
+
+    void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
+
+private:
+    friend class RenderThread;
+
+    EglManager(RenderThread& thread);
+    // EglContext is never destroyed, method is purposely not implemented
+    ~EglManager();
+
+    void loadConfig();
+    void createContext();
+    void initAtlas();
+
+    RenderThread& mRenderThread;
+
+    EGLDisplay mEglDisplay;
+    EGLConfig mEglConfig;
+    EGLContext mEglContext;
+    EGLSurface mPBufferSurface;
+
+    const bool mRequestDirtyRegions;
+    bool mCanSetDirtyRegions;
+
+    EGLSurface mCurrentSurface;
+
+    sp<GraphicBuffer> mAtlasBuffer;
+    int64_t* mAtlasMap;
+    size_t mAtlasMapSize;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* EGLMANAGER_H */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0901963..f90a26a 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -47,13 +47,13 @@
 
 #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()
 
-CREATE_BRIDGE2(createContext, bool translucent, RenderNode* rootRenderNode) {
-    return new CanvasContext(args->translucent, args->rootRenderNode);
+CREATE_BRIDGE3(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode) {
+    return new CanvasContext(*args->thread, args->translucent, args->rootRenderNode);
 }
 
 RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode)
@@ -62,6 +62,7 @@
     SETUP_TASK(createContext);
     args->translucent = translucent;
     args->rootRenderNode = rootRenderNode;
+    args->thread = &mRenderThread;
     mContext = (CanvasContext*) postAndWait(task);
     mDrawFrameTask.setContext(&mRenderThread, mContext);
 }
@@ -180,8 +181,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);
 }
@@ -200,20 +200,29 @@
     postAndWait(task);
 }
 
-CREATE_BRIDGE2(invokeFunctor, CanvasContext* context, Functor* functor) {
-    args->context->invokeFunctor(args->functor);
+CREATE_BRIDGE2(invokeFunctor, RenderThread* thread, Functor* functor) {
+    CanvasContext::invokeFunctor(*args->thread, args->functor);
     return NULL;
 }
 
 void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
     ATRACE_CALL();
+    RenderThread& thread = RenderThread::getInstance();
     SETUP_TASK(invokeFunctor);
-    args->context = mContext;
+    args->thread = &thread;
     args->functor = functor;
     if (waitForCompletion) {
-        postAndWait(task);
+        // waitForCompletion = true is expected to be fairly rare and only
+        // happen in destruction. Thus it should be fine to temporarily
+        // create a Mutex
+        Mutex mutex;
+        Condition condition;
+        SignalingRenderTask syncTask(task, &mutex, &condition);
+        AutoMutex _lock(mutex);
+        thread.queue(&syncTask);
+        condition.wait(mutex);
     } else {
-        post(task);
+        thread.queue(task);
     }
 }
 
@@ -234,7 +243,7 @@
     return NULL;
 }
 
-static void enqueueDestroyLayer(Layer* layer) {
+void RenderProxy::enqueueDestroyLayer(Layer* layer) {
     SETUP_TASK(destroyLayer);
     args->layer = layer;
     RenderThread::getInstance().queue(task);
@@ -243,7 +252,7 @@
 CREATE_BRIDGE3(createDisplayListLayer, CanvasContext* context, int width, int height) {
     Layer* layer = args->context->createRenderLayer(args->width, args->height);
     if (!layer) return 0;
-    return new DeferredLayerUpdater(layer, enqueueDestroyLayer);
+    return new DeferredLayerUpdater(layer, RenderProxy::enqueueDestroyLayer);
 }
 
 DeferredLayerUpdater* RenderProxy::createDisplayListLayer(int width, int height) {
@@ -259,7 +268,7 @@
 CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
     Layer* layer = args->context->createTextureLayer();
     if (!layer) return 0;
-    return new DeferredLayerUpdater(layer, enqueueDestroyLayer);
+    return new DeferredLayerUpdater(layer, RenderProxy::enqueueDestroyLayer);
 }
 
 DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -337,6 +346,22 @@
     postAndWait(task);
 }
 
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+    CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
+    args->buffer->decStrong(0);
+    return NULL;
+}
+
+void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) {
+    SETUP_TASK(setTextureAtlas);
+    args->thread = &mRenderThread;
+    args->buffer = buffer.get();
+    args->buffer->incStrong(0);
+    args->map = map;
+    args->size = size;
+    post(task);
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 944ff9c..df0aff0 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -70,13 +70,14 @@
     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);
+    ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
 
     ANDROID_API void runWithGlContext(RenderTask* task);
 
+    static void enqueueDestroyLayer(Layer* layer);
     ANDROID_API DeferredLayerUpdater* createDisplayListLayer(int width, int height);
     ANDROID_API DeferredLayerUpdater* createTextureLayer();
     ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
@@ -90,6 +91,8 @@
 
     ANDROID_API void dumpProfileInfo(int fd);
 
+    ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
+
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 4a4e254..03e98d5 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -21,7 +21,9 @@
 #include <gui/DisplayEventReceiver.h>
 #include <utils/Log.h>
 
+#include "../RenderState.h"
 #include "CanvasContext.h"
+#include "EglManager.h"
 #include "RenderProxy.h"
 
 namespace android {
@@ -138,13 +140,16 @@
         , mDisplayEventReceiver(0)
         , mVsyncRequested(false)
         , mFrameCallbackTaskPending(false)
-        , mFrameCallbackTask(0) {
+        , mFrameCallbackTask(0)
+        , mRenderState(NULL)
+        , mEglManager(NULL) {
     mFrameCallbackTask = new DispatchFrameCallbacks(this);
     mLooper = new Looper(false);
     run("RenderThread");
 }
 
 RenderThread::~RenderThread() {
+    LOG_ALWAYS_FATAL("Can't destroy the render thread");
 }
 
 void RenderThread::initializeDisplayEventReceiver() {
@@ -159,6 +164,12 @@
             Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
 }
 
+void RenderThread::initThreadLocals() {
+    initializeDisplayEventReceiver();
+    mEglManager = new EglManager(*this);
+    mRenderState = new RenderState();
+}
+
 int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
     if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
         ALOGE("Display event receiver pipe was closed or an error occurred.  "
@@ -233,7 +244,7 @@
 }
 
 bool RenderThread::threadLoop() {
-    initializeDisplayEventReceiver();
+    initThreadLocals();
 
     int timeoutMillis = -1;
     for (;;) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4412584..0b91e9d 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -31,12 +31,18 @@
 #include "TimeLord.h"
 
 namespace android {
+
 class DisplayEventReceiver;
 
 namespace uirenderer {
+
+class RenderState;
+
 namespace renderthread {
 
 class DispatchFrameCallbacks;
+class EglManager;
+class RenderProxy;
 
 class TaskQueue {
 public:
@@ -62,7 +68,7 @@
     ~IFrameCallback() {}
 };
 
-class ANDROID_API RenderThread : public Thread, public Singleton<RenderThread> {
+class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
 public:
     // RenderThread takes complete ownership of tasks that are queued
     // and will delete them after they are run
@@ -79,6 +85,8 @@
     void pushBackFrameCallback(IFrameCallback* callback);
 
     TimeLord& timeLord() { return mTimeLord; }
+    RenderState& renderState() { return *mRenderState; }
+    EglManager& eglManager() { return *mEglManager; }
 
 protected:
     virtual bool threadLoop();
@@ -86,10 +94,12 @@
 private:
     friend class Singleton<RenderThread>;
     friend class DispatchFrameCallbacks;
+    friend class RenderProxy;
 
     RenderThread();
     virtual ~RenderThread();
 
+    void initThreadLocals();
     void initializeDisplayEventReceiver();
     static int displayEventReceiverCallback(int fd, int events, void* data);
     void drainDisplayEventQueue(bool skipCallbacks = false);
@@ -119,6 +129,8 @@
     DispatchFrameCallbacks* mFrameCallbackTask;
 
     TimeLord mTimeLord;
+    RenderState* mRenderState;
+    EglManager* mEglManager;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
index 9a211a2..30b6ff2 100644
--- a/libs/hwui/thread/Task.h
+++ b/libs/hwui/thread/Task.h
@@ -17,8 +17,6 @@
 #ifndef ANDROID_HWUI_TASK_H
 #define ANDROID_HWUI_TASK_H
 
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
 #include <utils/RefBase.h>
 #include <utils/Trace.h>
 
@@ -40,7 +38,7 @@
     virtual ~Task() { }
 
     T getResult() const {
-        ATRACE_NAME("waitForTask");
+        ScopedTrace tracer(ATRACE_TAG_VIEW, "waitForTask");
         return mFuture->get();
     }
 
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/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 3f6ccc9..063383b 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -206,9 +206,8 @@
             } else {
                 SkBitmap surfaceBitmap;
                 ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
-                surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
-                        outBuffer.width, outBuffer.height, bpr);
-                surfaceBitmap.setPixels(outBuffer.bits);
+                surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height),
+                                            outBuffer.bits, bpr);
 
                 SkCanvas surfaceCanvas(surfaceBitmap);
 
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index f70110c..bdd1195 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -583,7 +583,8 @@
     }
 
     /**
-     * Get the altitude if available, in meters above sea level.
+     * Get the altitude if available, in meters above the WGS 84 reference
+     * ellipsoid.
      *
      * <p>If this location does not have an altitude then 0.0 is returned.
      */
@@ -592,7 +593,7 @@
     }
 
     /**
-     * Set the altitude, in meters above sea level.
+     * Set the altitude, in meters above the WGS 84 reference ellipsoid.
      *
      * <p>Following this call {@link #hasAltitude} will return true.
      */
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 5a3aaab..57c66da 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -17,20 +17,22 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 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";
 
     /**
@@ -153,6 +155,7 @@
     private int mContentType = CONTENT_TYPE_UNKNOWN;
     private int mFlags = 0x0;
     private HashSet<String> mTags;
+    private String mFormattedTags;
 
     private AudioAttributes() {
     }
@@ -193,6 +196,7 @@
     }
 
     /**
+     * @hide
      * Return the set of tags.
      * @return a read-only set of all tags stored as strings.
      */
@@ -239,6 +243,12 @@
             aa.mUsage = mUsage;
             aa.mFlags = mFlags;
             aa.mTags = (HashSet<String>) mTags.clone();
+            final Iterator<String> tagIterator = mTags.iterator();
+            String allTagsInOne = new String();
+            while (tagIterator.hasNext()) {
+                allTagsInOne += tagIterator.next() + ";";
+            }
+            aa.mFormattedTags = allTagsInOne;
             return aa;
         }
 
@@ -325,6 +335,7 @@
         }
 
         /**
+         * @hide
          * Add a custom tag stored as a string
          * @param tag
          * @return the same Builder instance.
@@ -411,6 +422,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..a471e83 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -16,14 +16,19 @@
 
 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
  * in {@link AudioTrack} and {@link AudioRecord}.
- * 
+ *
  */
 public class AudioFormat {
-    
+
     //---------------------------------------------------------
     // Constants
     //--------------------
@@ -39,6 +44,10 @@
     public static final int ENCODING_PCM_8BIT = 3;
     /** Audio data format: single-precision floating-point per sample */
     public static final int ENCODING_PCM_FLOAT = 4;
+    /** Audio data format: AC-3 compressed */
+    public static final int ENCODING_AC3 = 5;
+    /** Audio data format: E-AC-3 compressed */
+    public static final int ENCODING_E_AC3 = 6;
 
     /** Invalid audio channel configuration */
     /** @deprecated use CHANNEL_INVALID instead  */
@@ -150,10 +159,220 @@
         case ENCODING_PCM_16BIT:
         case ENCODING_DEFAULT:
             return 2;
+        case ENCODING_PCM_FLOAT:
+            return 4;
         case ENCODING_INVALID:
         default:
             throw new IllegalArgumentException("Bad audio format " + audioFormat);
         }
     }
 
+    /** @hide */
+    public static boolean isValidEncoding(int audioFormat)
+    {
+        switch (audioFormat) {
+        case ENCODING_PCM_8BIT:
+        case ENCODING_PCM_16BIT:
+        case ENCODING_PCM_FLOAT:
+        case ENCODING_AC3:
+        case ENCODING_E_AC3:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    /** @hide */
+    public static boolean isEncodingLinearPcm(int audioFormat)
+    {
+        switch (audioFormat) {
+        case ENCODING_PCM_8BIT:
+        case ENCODING_PCM_16BIT:
+        case ENCODING_PCM_FLOAT:
+        case ENCODING_DEFAULT:
+            return true;
+        case ENCODING_AC3:
+        case ENCODING_E_AC3:
+            return false;
+        case ENCODING_INVALID:
+        default:
+            throw new IllegalArgumentException("Bad audio format " + audioFormat);
+        }
+    }
+
+    /** @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;
+
+    int getEncoding() {
+        return mEncoding;
+    }
+
+    int getSampleRate() {
+        return mSampleRate;
+    }
+
+    int getChannelMask() {
+        return mChannelMask;
+    }
+
+    int getPropertySetMask() {
+        return 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},
+         *     {@link AudioFormat#ENCODING_AC3},
+         *     {@link AudioFormat#ENCODING_E_AC3}.
+         * @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:
+                case ENCODING_AC3:
+                case ENCODING_E_AC3:
+                    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,
+        ENCODING_AC3,
+        ENCODING_E_AC3
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Encoding {}
+
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 458139b..c8b51e0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -50,12 +50,6 @@
  */
 public class AudioManager {
 
-    // If we should use the new sessions APIs.
-    private final static boolean USE_SESSIONS = true;
-    // If we should use the legacy APIs. If both are true information will be
-    // duplicated through both paths. Currently this flag isn't used.
-    private final static boolean USE_LEGACY = true;
-
     private final Context mContext;
     private long mVolumeKeyUpTime;
     private final boolean mUseMasterVolume;
@@ -483,17 +477,8 @@
      *     or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
      */
     public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
-        if (USE_SESSIONS) {
-            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-            helper.sendMediaButtonEvent(keyEvent, false);
-        } else {
-            IAudioService service = getService();
-            try {
-                service.dispatchMediaKeyEvent(keyEvent);
-            } catch (RemoteException e) {
-                Log.e(TAG, "dispatchMediaKeyEvent threw exception ", e);
-            }
-        }
+        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+        helper.sendMediaButtonEvent(keyEvent, false);
     }
 
     /**
@@ -644,12 +629,8 @@
             if (mUseMasterVolume) {
                 service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
             } else {
-                if (USE_SESSIONS) {
-                    MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-                    helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
-                } else {
-                    service.adjustVolume(direction, flags, mContext.getOpPackageName());
-                }
+                MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+                helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustVolume", e);
@@ -679,13 +660,8 @@
             if (mUseMasterVolume) {
                 service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
             } else {
-                if (USE_SESSIONS) {
-                    MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-                    helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
-                } else {
-                    service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
-                            mContext.getOpPackageName());
-                }
+                MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+                helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
@@ -1017,7 +993,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);
         }
@@ -1278,11 +1254,6 @@
      * call {@link #stopBluetoothSco()} to clear the request and turn down the bluetooth connection.
      * <p>Even if a SCO connection is established, the following restrictions apply on audio
      * output streams so that they can be routed to SCO headset:
-     * <p>NOTE: up to and including API version
-     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
-     * voice call to the bluetooth headset.
-     * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
-     * connection is established.
      * <ul>
      *   <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
      *   <li> the format must be mono </li>
@@ -1298,6 +1269,11 @@
      * it will be ignored. Similarly, if a call is received or sent while an application
      * is using the SCO connection, the connection will be lost for the application and NOT
      * returned automatically when the call ends.
+     * <p>NOTE: up to and including API version
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method initiates a virtual
+     * voice call to the bluetooth headset.
+     * After API version {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} only a raw SCO audio
+     * connection is established.
      * @see #stopBluetoothSco()
      * @see #ACTION_SCO_AUDIO_STATE_UPDATED
      */
@@ -1311,13 +1287,38 @@
     }
 
     /**
+     * Start bluetooth SCO audio connection in virtual call mode.
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+     * <p>Similar to {@link #startBluetoothSco()} with explicit selection of virtual call mode.
+     * Telephony and communication applications (VoIP, Video Chat) should preferably select
+     * virtual call mode.
+     * Applications using voice input for search or commands should first try raw audio connection
+     * with {@link #startBluetoothSco()} and fall back to startBluetoothScoVirtualCall() in case of
+     * failure.
+     * @see #startBluetoothSco()
+     * @see #stopBluetoothSco()
+     * @see #ACTION_SCO_AUDIO_STATE_UPDATED
+     */
+    public void startBluetoothScoVirtualCall() {
+        IAudioService service = getService();
+        try {
+            service.startBluetoothScoVirtualCall(mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in startBluetoothScoVirtualCall", e);
+        }
+    }
+
+    /**
      * Stop bluetooth SCO audio connection.
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
      * <p>This method must be called by applications having requested the use of
-     * bluetooth SCO audio with {@link #startBluetoothSco()}
-     * when finished with the SCO connection or if connection fails.
+     * bluetooth SCO audio with {@link #startBluetoothSco()} or
+     * {@link #startBluetoothScoVirtualCall()} when finished with the SCO connection or
+     * if connection fails.
      * @see #startBluetoothSco()
+     * @see #startBluetoothScoVirtualCall()
      */
     public void stopBluetoothSco(){
         IAudioService service = getService();
@@ -1425,7 +1426,12 @@
      *           <var>false</var> to turn mute off
      */
     public void setMicrophoneMute(boolean on){
-        AudioSystem.muteMicrophone(on);
+        IAudioService service = getService();
+        try {
+            service.setMicrophoneMute(on, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setMicrophoneMute", e);
+        }
     }
 
     /**
@@ -1635,24 +1641,23 @@
     }
 
     /**
-     * @hide
-     * If the stream is active locally or remotely, adjust its volume according to the enforced
-     * priority rules.
-     * Note: only AudioManager.STREAM_MUSIC is supported at the moment
+     * 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 void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
-        if (streamType != STREAM_MUSIC) {
-            Log.w(TAG, "adjustLocalOrRemoteStreamVolume() doesn't support stream " + streamType);
-        }
-        IAudioService service = getService();
-        try {
-            service.adjustLocalOrRemoteStreamVolume(streamType, direction,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e);
+    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
@@ -2189,49 +2194,8 @@
             Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
             return;
         }
-        IAudioService service = getService();
-        try {
-            // pi != null
-            service.registerMediaButtonIntent(pi, eventReceiver,
-                    eventReceiver == null ? mToken : null);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
-        }
-        if (USE_SESSIONS) {
-            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-            helper.addMediaButtonListener(pi, mContext);
-        }
-    }
-
-    /**
-     * @hide
-     * Used internally by telephony package to register an intent receiver for ACTION_MEDIA_BUTTON.
-     * @param eventReceiver the component that will receive the media button key events,
-     *          no-op if eventReceiver is null
-     */
-    public void registerMediaButtonEventReceiverForCalls(ComponentName eventReceiver) {
-        if (eventReceiver == null) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            // eventReceiver != null
-            service.registerMediaButtonEventReceiverForCalls(eventReceiver);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerMediaButtonEventReceiverForCalls", e);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void unregisterMediaButtonEventReceiverForCalls() {
-        IAudioService service = getService();
-        try {
-            service.unregisterMediaButtonEventReceiverForCalls();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiverForCalls", e);
-        }
+        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+        helper.addMediaButtonListener(pi, eventReceiver, mContext);
     }
 
     /**
@@ -2268,16 +2232,8 @@
      * @hide
      */
     public void unregisterMediaButtonIntent(PendingIntent pi) {
-        IAudioService service = getService();
-        try {
-            service.unregisterMediaButtonIntent(pi);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
-        }
-        if (USE_SESSIONS) {
-            MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
-            helper.removeMediaButtonListener(pi);
-        }
+        MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+        helper.removeMediaButtonListener(pi);
     }
 
     /**
@@ -2291,20 +2247,7 @@
         if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
             return;
         }
-        IAudioService service = getService();
-        try {
-            int rcseId = service.registerRemoteControlClient(
-                    rcClient.getRcMediaIntent(),       /* mediaIntent   */
-                    rcClient.getIRemoteControlClient(),/* rcClient      */
-                    // used to match media button event receiver and audio focus
-                    mContext.getPackageName());        /* packageName   */
-            rcClient.setRcseId(rcseId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
-        }
-        if (USE_SESSIONS) {
-            rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mContext));
-        }
+        rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mContext));
     }
 
     /**
@@ -2317,16 +2260,7 @@
         if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
             return;
         }
-        IAudioService service = getService();
-        try {
-            service.unregisterRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent   */
-                    rcClient.getIRemoteControlClient());                       /* rcClient      */
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
-        }
-        if (USE_SESSIONS) {
-            rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mContext));
-        }
+        rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mContext));
     }
 
     /**
@@ -2344,25 +2278,8 @@
         if (rctlr == null) {
             return false;
         }
-        if (USE_SESSIONS) {
-            rctlr.startListeningToSessions();
-            return true;
-        } else {
-            IAudioService service = getService();
-            final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
-            final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
-            try {
-                int[] artworkDimensions = rctlr.getArtworkSize();
-                boolean reg = service.registerRemoteController(rctlr.getRcDisplay(),
-                        artworkDimensions[0]/* w */, artworkDimensions[1]/* h */,
-                        listenerComponent);
-                rctlr.setIsRegistered(reg);
-                return reg;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in registerRemoteController " + e);
-                return false;
-            }
-        }
+        rctlr.startListeningToSessions();
+        return true;
     }
 
     /**
@@ -2374,17 +2291,7 @@
         if (rctlr == null) {
             return;
         }
-        if (USE_SESSIONS) {
-            rctlr.stopListeningToSessions();
-        } else {
-            IAudioService service = getService();
-            try {
-                service.unregisterRemoteControlDisplay(rctlr.getRcDisplay());
-                rctlr.setIsRegistered(false);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
-            }
-        }
+        rctlr.stopListeningToSessions();
     }
 
     /**
@@ -2490,46 +2397,6 @@
     }
 
     /**
-     * @hide
-     * Request the user of a RemoteControlClient to seek to the given playback position.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param timeMs the time in ms to seek to, must be positive.
-     */
-    public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        if (timeMs < 0) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.setRemoteControlClientPlaybackPosition(generationId, timeMs);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", "
-                    + timeMs + ")", e);
-        }
-    }
-
-    /**
-     * @hide
-     * Notify the user of a RemoteControlClient that it should update its metadata with the
-     * new value for the given key.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param key the metadata key for which a new value exists
-     * @param value the new metadata value
-     */
-    public void updateRemoteControlClientMetadata(int generationId, int key,
-            Rating value) {
-        IAudioService service = getService();
-        try {
-            service.updateRemoteControlClientMetadata(generationId, key, value);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in updateRemoteControlClientMetadata("+ generationId + ", "
-                    + key +", " + value + ")", e);
-        }
-    }
-
-    /**
      *  @hide
      *  Reload audio settings. This method is called by Settings backup
      *  agent when audio settings are restored and causes the AudioService
@@ -2586,6 +2453,9 @@
     // from AudioManager. AudioSystem is an internal class used by AudioManager and AudioService.
 
     /** @hide
+     * The audio device code for representing "no device." */
+    public static final int DEVICE_NONE = AudioSystem.DEVICE_NONE;
+    /** @hide
      *  The audio output device code for the small speaker at the front of the device used
      *  when placing calls.  Does not refer to an in-ear headphone without attached microphone,
      *  such as earbuds, earphones, or in-ear monitors (IEM). Those would be handled as a
@@ -2846,18 +2716,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 {
@@ -2937,44 +2811,6 @@
      * Only useful for volume controllers.
      * @hide
      */
-    public int getRemoteStreamVolume() {
-        try {
-            return getService().getRemoteStreamVolume();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error getting remote stream volume", e);
-            return 0;
-        }
-    }
-
-    /**
-     * Only useful for volume controllers.
-     * @hide
-     */
-    public int getRemoteStreamMaxVolume() {
-        try {
-            return getService().getRemoteStreamMaxVolume();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error getting remote stream max volume", e);
-            return 0;
-        }
-    }
-
-    /**
-     * Only useful for volume controllers.
-     * @hide
-     */
-    public void setRemoteStreamVolume(int index) {
-        try {
-            getService().setRemoteStreamVolume(index);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error setting remote stream volume", e);
-        }
-    }
-
-    /**
-     * Only useful for volume controllers.
-     * @hide
-     */
     public boolean isStreamAffectedByRingerMode(int streamType) {
         try {
             return getService().isStreamAffectedByRingerMode(streamType);
@@ -3003,7 +2839,8 @@
     /** @hide
      */
     public static final int SUCCESS = AudioSystem.SUCCESS;
-    /** @hide
+    /**
+     * A default error code.
      */
     public static final int ERROR = AudioSystem.ERROR;
     /** @hide
@@ -3018,7 +2855,9 @@
     /** @hide
      */
     public static final int ERROR_NO_INIT = AudioSystem.NO_INIT;
-    /** @hide
+    /**
+     * An error code indicating that the object reporting it is no longer valid and needs to
+     * be recreated.
      */
     public static final int ERROR_DEAD_OBJECT = AudioSystem.DEAD_OBJECT;
 
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index fd346d5..d882bc8 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -85,6 +85,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -108,8 +109,7 @@
     /** Debug volumes */
     protected static final boolean DEBUG_VOL = false;
 
-    /** Reroute calls to media session apis */
-    private static final boolean USE_SESSIONS = true;
+    /** debug calls to media session apis */
     private static final boolean DEBUG_SESSIONS = true;
 
     /** Allow volume changes to set ringer mode to silent? */
@@ -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;
@@ -443,8 +444,7 @@
     // Devices for which the volume is fixed and VolumePanel slider should be disabled
     final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
             AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ALL_USB;
+            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
 
     // TODO merge orientation and rotation
     private final boolean mMonitorOrientation;
@@ -830,24 +830,6 @@
     }
 
     /** @see AudioManager#adjustVolume(int, int) */
-    public void adjustVolume(int direction, int flags, String callingPackage) {
-        adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags,
-                callingPackage);
-    }
-
-    /** @see AudioManager#adjustLocalOrRemoteStreamVolume(int, int) with current assumption
-     *  on streamType: fixed to STREAM_MUSIC */
-    public void adjustLocalOrRemoteStreamVolume(int streamType, int direction,
-            String callingPackage) {
-        if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")");
-        if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
-            adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0, callingPackage);
-        } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
-            mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0);
-        }
-    }
-
-    /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage) {
         if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType);
@@ -867,10 +849,8 @@
         }
 
         if (streamType == STREAM_REMOTE_MUSIC) {
-            // don't play sounds for remote
-            flags &= ~(AudioManager.FLAG_PLAY_SOUND|AudioManager.FLAG_FIXED_VOLUME);
-            //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()");
-            mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags);
+            // TODO bounce it to MediaSessionService to find an appropriate
+            // session
         } else {
             adjustStreamVolume(streamType, direction, flags, callingPackage);
         }
@@ -1320,11 +1300,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
@@ -1431,6 +1416,16 @@
         }
     }
 
+    /** @see AudioManager#setMicrophoneMute(boolean) */
+    public void setMicrophoneMute(boolean on, String callingPackage) {
+        if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, Binder.getCallingUid(),
+                callingPackage) != AppOpsManager.MODE_ALLOWED) {
+            return;
+        }
+
+        AudioSystem.muteMicrophone(on);
+    }
+
     /** @see AudioManager#getRingerMode() */
     public int getRingerMode() {
         synchronized(mSettingsLock) {
@@ -2059,7 +2054,19 @@
     }
 
     /** @see AudioManager#startBluetoothSco() */
-    public void startBluetoothSco(IBinder cb, int targetSdkVersion){
+    public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+        int scoAudioMode =
+                (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
+                        SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+        startBluetoothScoInt(cb, scoAudioMode);
+    }
+
+    /** @see AudioManager#startBluetoothScoVirtualCall() */
+    public void startBluetoothScoVirtualCall(IBinder cb) {
+        startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+    }
+
+    void startBluetoothScoInt(IBinder cb, int scoAudioMode){
         if (!checkAudioSettingsPermission("startBluetoothSco()") ||
                 !mSystemReady) {
             return;
@@ -2071,7 +2078,7 @@
         // The caller identity must be cleared after getScoClient() because it is needed if a new
         // client is created.
         final long ident = Binder.clearCallingIdentity();
-        client.incCount(targetSdkVersion);
+        client.incCount(scoAudioMode);
         Binder.restoreCallingIdentity(ident);
     }
 
@@ -2117,9 +2124,9 @@
             }
         }
 
-        public void incCount(int targetSdkVersion) {
+        public void incCount(int scoAudioMode) {
             synchronized(mScoClients) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, targetSdkVersion);
+                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
                 if (mStartcount == 0) {
                     try {
                         mCb.linkToDeath(this, 0);
@@ -2189,7 +2196,7 @@
             }
         }
 
-        private void requestScoState(int state, int targetSdkVersion) {
+        private void requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
             if (totalCount() == 0) {
                 if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
@@ -2204,9 +2211,7 @@
                                 (mScoAudioState == SCO_STATE_INACTIVE ||
                                  mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
                             if (mScoAudioState == SCO_STATE_INACTIVE) {
-                                mScoAudioMode =
-                                        (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
-                                                SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+                                mScoAudioMode = scoAudioMode;
                                 if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
                                     boolean status;
                                     if (mScoAudioMode == SCO_MODE_RAW) {
@@ -2384,10 +2389,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 +2402,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 +2486,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 +2910,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 +2971,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 +3038,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 +3075,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 +3781,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 +3914,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 +3940,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 +4003,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 +4094,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 +4162,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 +4181,7 @@
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
             }
-            if (!isUsb) {
+            if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
                 sendDeviceConnectionIntent(device, state, name);
             }
         }
@@ -4093,7 +4197,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 +4233,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 +4248,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 +4261,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 +4283,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 +4299,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;
@@ -4302,92 +4410,12 @@
         mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
     }
 
-    public void registerMediaButtonEventReceiverForCalls(ComponentName c) {
-        mMediaFocusControl.registerMediaButtonEventReceiverForCalls(c);
-    }
-
-    public void unregisterMediaButtonEventReceiverForCalls() {
-        mMediaFocusControl.unregisterMediaButtonEventReceiverForCalls();
-    }
-
-    public void registerMediaButtonIntent(PendingIntent pi, ComponentName c, IBinder token) {
-        mMediaFocusControl.registerMediaButtonIntent(pi, c, token);
-    }
-
-    public void unregisterMediaButtonIntent(PendingIntent pi) {
-        mMediaFocusControl.unregisterMediaButtonIntent(pi);
-    }
-
-    public int registerRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient, String callingPckg) {
-        return mMediaFocusControl.registerRemoteControlClient(mediaIntent, rcClient, callingPckg);
-    }
-
-    public void unregisterRemoteControlClient(PendingIntent mediaIntent,
-            IRemoteControlClient rcClient) {
-        mMediaFocusControl.unregisterRemoteControlClient(mediaIntent, rcClient);
-    }
-
-    public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs);
-    }
-
-    public void updateRemoteControlClientMetadata(int generationId, int key, Rating value) {
-        mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value);
-    }
-
-    public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
-        mMediaFocusControl.registerRemoteVolumeObserverForRcc(rccId, rvo);
-    }
-
-    @Override
-    public int getRemoteStreamVolume() {
-        return mMediaFocusControl.getRemoteStreamVolume();
-    }
-
-    @Override
-    public int getRemoteStreamMaxVolume() {
-        return mMediaFocusControl.getRemoteStreamMaxVolume();
-    }
-
     @Override
     public void setRemoteStreamVolume(int index) {
         enforceSelfOrSystemUI("set the remote stream volume");
         mMediaFocusControl.setRemoteStreamVolume(index);
     }
 
-    public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
-        mMediaFocusControl.setPlaybackStateForRcc(rccId, state, timeMs, speed);
-    }
-
-    public void setPlaybackInfoForRcc(int rccId, int what, int value) {
-        mMediaFocusControl.setPlaybackInfoForRcc(rccId, what, value);
-    }
-
-    public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
-        if (USE_SESSIONS) {
-            if (DEBUG_SESSIONS) {
-                int pid = getCallingPid();
-                Log.w(TAG, "Call to dispatchMediaKeyEvent from " + pid);
-            }
-            MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
-        } else {
-            mMediaFocusControl.dispatchMediaKeyEvent(keyEvent);
-        }
-    }
-
-    public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
-        if (USE_SESSIONS) {
-            if (DEBUG_SESSIONS) {
-                int pid = getCallingPid();
-                Log.w(TAG, "Call to dispatchMediaKeyEventUnderWakelock from " + pid);
-            }
-            MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, true);
-        } else {
-            mMediaFocusControl.dispatchMediaKeyEventUnderWakelock(keyEvent);
-        }
-    }
-
     //==========================================================================================
     // Audio Focus
     //==========================================================================================
@@ -4764,4 +4792,91 @@
         }
         mVolumeController.setController(controller);
     }
+
+    public static class VolumeController {
+        private static final String TAG = "VolumeController";
+
+        private IVolumeController mController;
+
+        public void setController(IVolumeController controller) {
+            mController = controller;
+        }
+
+        public boolean isSameBinder(IVolumeController controller) {
+            return Objects.equals(asBinder(), binder(controller));
+        }
+
+        public IBinder asBinder() {
+            return binder(mController);
+        }
+
+        private static IBinder binder(IVolumeController controller) {
+            return controller == null ? null : controller.asBinder();
+        }
+
+        @Override
+        public String toString() {
+            return "VolumeController(" + asBinder() + ")";
+        }
+
+        public void postDisplaySafeVolumeWarning(int flags) {
+            if (mController == null)
+                return;
+            try {
+                mController.displaySafeVolumeWarning(flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling displaySafeVolumeWarning", e);
+            }
+        }
+
+        public void postVolumeChanged(int streamType, int flags) {
+            if (mController == null)
+                return;
+            try {
+                mController.volumeChanged(streamType, flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling volumeChanged", e);
+            }
+        }
+
+        public void postMasterVolumeChanged(int flags) {
+            if (mController == null)
+                return;
+            try {
+                mController.masterVolumeChanged(flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling masterVolumeChanged", e);
+            }
+        }
+
+        public void postMasterMuteChanged(int flags) {
+            if (mController == null)
+                return;
+            try {
+                mController.masterMuteChanged(flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling masterMuteChanged", e);
+            }
+        }
+
+        public void setLayoutDirection(int layoutDirection) {
+            if (mController == null)
+                return;
+            try {
+                mController.setLayoutDirection(layoutDirection);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling setLayoutDirection", e);
+            }
+        }
+
+        public void postDismiss() {
+            if (mController == null)
+                return;
+            try {
+                mController.dismiss();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling dismiss", e);
+            }
+        }
+    }
 }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af7a3e1..d3e0f48 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -28,11 +28,13 @@
  */
 public class AudioSystem
 {
-    /* These values must be kept in sync with AudioSystem.h */
+    /* These values must be kept in sync with system/audio.h */
     /*
      * If these are modified, please also update Settings.System.VOLUME_SETTINGS
      * and attrs.xml and AudioManager.java.
      */
+    /* The default audio stream */
+    public static final int STREAM_DEFAULT = -1;
     /* The audio stream for phone calls */
     public static final int STREAM_VOICE_CALL = 0;
     /* The audio stream for system sounds */
@@ -130,6 +132,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
      *
@@ -220,6 +227,7 @@
     // audio device definitions: must be kept in sync with values in system/core/audio.h
     //
 
+    public static final int DEVICE_NONE = 0x0;
     // reserved bits
     public static final int DEVICE_BIT_IN = 0x80000000;
     public static final int DEVICE_BIT_DEFAULT = 0x40000000;
@@ -300,7 +308,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 +328,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..3f79e9a 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -21,6 +21,8 @@
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.nio.NioUtils;
+import java.util.Iterator;
+import java.util.Set;
 
 import android.annotation.IntDef;
 import android.app.ActivityThread;
@@ -233,6 +235,8 @@
      *   {@link AudioManager#STREAM_DTMF}.
      */
     private int mStreamType = AudioManager.STREAM_MUSIC;
+
+    private final AudioAttributes mAttributes;
     /**
      * The way audio is consumed by the audio sink, streaming or static.
      */
@@ -349,21 +353,69 @@
             int bufferSizeInBytes, int mode, int sessionId)
     throws IllegalArgumentException {
         // mState already == STATE_UNINITIALIZED
+        this((new AudioAttributes.Builder())
+                    .setLegacyStreamType(streamType)
+                    .build(),
+                (new AudioFormat.Builder())
+                    .setChannelMask(channelConfig)
+                    .setEncoding(audioFormat)
+                    .setSampleRate(sampleRateInHz)
+                    .build(),
+                bufferSizeInBytes,
+                mode, sessionId);
+    }
+
+    /**
+     * @hide
+     * CANDIDATE FOR PUBLIC API
+     * Constructor with AudioAttributes and AudioFormat
+     * @param aa
+     * @param format
+     * @param bufferSizeInBytes
+     * @param mode
+     * @param sessionId
+     */
+    public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
+            int mode, int sessionId)
+                    throws IllegalArgumentException {
+        // mState already == STATE_UNINITIALIZED
 
         // remember which looper is associated with the AudioTrack instantiation
         Looper looper;
         if ((looper = Looper.myLooper()) == null) {
             looper = Looper.getMainLooper();
         }
-        mInitializationLooper = looper;
 
-        audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
+        int rate = 0;
+        if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
+        {
+            rate = format.getSampleRate();
+        } else {
+            rate = AudioSystem.getPrimaryOutputSamplingRate();
+            if (rate <= 0) {
+                rate = 44100;
+            }
+        }
+        int channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT;
+        if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0)
+        {
+            channelMask = format.getChannelMask();
+        }
+        int encoding = AudioFormat.ENCODING_DEFAULT;
+        if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0) {
+            encoding = format.getEncoding();
+        }
+        audioParamCheck(rate, channelMask, encoding, mode);
+        mStreamType = AudioSystem.STREAM_DEFAULT;
 
         audioBuffSizeCheck(bufferSizeInBytes);
 
+        mInitializationLooper = looper;
         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
         mAppOps = IAppOpsService.Stub.asInterface(b);
 
+        mAttributes = (new AudioAttributes.Builder(attributes).build());
+
         if (sessionId < 0) {
             throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
         }
@@ -371,8 +423,8 @@
         int[] session = new int[1];
         session[0] = sessionId;
         // native initialization
-        int initResult = native_setup(new WeakReference<AudioTrack>(this),
-                mStreamType, mSampleRate, mChannels, mAudioFormat,
+        int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
+                mSampleRate, mChannels, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session);
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
@@ -401,27 +453,13 @@
     // Convenience method for the constructor's parameter checks.
     // This is where constructor IllegalArgumentException-s are thrown
     // postconditions:
-    //    mStreamType is valid
     //    mChannelCount is valid
     //    mChannels is valid
     //    mAudioFormat is valid
     //    mSampleRate is valid
     //    mDataLoadMode is valid
-    private void audioParamCheck(int streamType, int sampleRateInHz,
+    private void audioParamCheck(int sampleRateInHz,
                                  int channelConfig, int audioFormat, int mode) {
-
-        //--------------
-        // stream type
-        if( (streamType != AudioManager.STREAM_ALARM) && (streamType != AudioManager.STREAM_MUSIC)
-           && (streamType != AudioManager.STREAM_RING) && (streamType != AudioManager.STREAM_SYSTEM)
-           && (streamType != AudioManager.STREAM_VOICE_CALL)
-           && (streamType != AudioManager.STREAM_NOTIFICATION)
-           && (streamType != AudioManager.STREAM_BLUETOOTH_SCO)
-           && (streamType != AudioManager.STREAM_DTMF)) {
-            throw new IllegalArgumentException("Invalid stream type.");
-        }
-        mStreamType = streamType;
-
         //--------------
         // sample rate, note these values are subject to change
         if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
@@ -457,25 +495,19 @@
 
         //--------------
         // audio format
-        switch (audioFormat) {
-        case AudioFormat.ENCODING_DEFAULT:
-            mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
-            break;
-        case AudioFormat.ENCODING_PCM_16BIT:
-        case AudioFormat.ENCODING_PCM_8BIT:
-        case AudioFormat.ENCODING_PCM_FLOAT:
-            mAudioFormat = audioFormat;
-            break;
-        default:
-            throw new IllegalArgumentException("Unsupported sample encoding."
-                + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT"
-                + " or ENCODING_PCM_FLOAT"
-                + ".");
+        if (audioFormat == AudioFormat.ENCODING_DEFAULT) {
+            audioFormat = AudioFormat.ENCODING_PCM_16BIT;
         }
 
+        if (!AudioFormat.isValidEncoding(audioFormat)) {
+            throw new IllegalArgumentException("Unsupported audio encoding.");
+        }
+        mAudioFormat = audioFormat;
+
         //--------------
         // audio load mode
-        if ( (mode != MODE_STREAM) && (mode != MODE_STATIC) ) {
+        if (((mode != MODE_STREAM) && (mode != MODE_STATIC)) ||
+                ((mode != MODE_STREAM) && !AudioFormat.isEncodingLinearPcm(mAudioFormat))) {
             throw new IllegalArgumentException("Invalid mode.");
         }
         mDataLoadMode = mode;
@@ -522,8 +554,13 @@
     private void audioBuffSizeCheck(int audioBufferSize) {
         // NB: this section is only valid with PCM data.
         //     To update when supporting compressed formats
-        int frameSizeInBytes = mChannelCount
-                * (AudioFormat.getBytesPerSample(mAudioFormat));
+        int frameSizeInBytes;
+        if (AudioFormat.isEncodingLinearPcm(mAudioFormat)) {
+            frameSizeInBytes = mChannelCount
+                    * (AudioFormat.getBytesPerSample(mAudioFormat));
+        } else {
+            frameSizeInBytes = 1;
+        }
         if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
             throw new IllegalArgumentException("Invalid audio buffer size.");
         }
@@ -757,9 +794,7 @@
             }
         }
 
-        if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT)
-            && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)
-            && (audioFormat != AudioFormat.ENCODING_PCM_FLOAT)) {
+        if (!AudioFormat.isValidEncoding(audioFormat)) {
             loge("getMinBufferSize(): Invalid audio format.");
             return ERROR_BAD_VALUE;
         }
@@ -816,6 +851,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.
@@ -1162,7 +1199,9 @@
      * @param sizeInBytes the number of bytes to read in audioData after the offset.
      * @return the number of bytes that were written or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
-     *    the parameters don't resolve to valid data and indexes.
+     *    the parameters don't resolve to valid data and indexes, or
+     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
+     *    needs to be recreated.
      */
 
     public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) {
@@ -1211,7 +1250,7 @@
 
     public int write(short[] audioData, int offsetInShorts, int sizeInShorts) {
 
-        if (mState == STATE_UNINITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
+        if (mState == STATE_UNINITIALIZED || mAudioFormat != AudioFormat.ENCODING_PCM_16BIT) {
             return ERROR_INVALID_OPERATION;
         }
 
@@ -1471,7 +1510,6 @@
         void onPeriodicNotification(AudioTrack track);
     }
 
-
     //---------------------------------------------------------
     // Inner classes
     //--------------------
@@ -1559,8 +1597,12 @@
     // Native methods called from the Java side
     //--------------------
 
-    private native final int native_setup(Object audiotrack_this,
-            int streamType, int sampleRate, int channelMask, int audioFormat,
+    // post-condition: mStreamType is overwritten with a value
+    //     that reflects the audio attributes (e.g. an AudioAttributes object with a usage of
+    //     AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC
+    private native final int native_setup(Object /*WeakReference<AudioTrack>*/ audiotrack_this,
+            Object /*AudioAttributes*/ attributes,
+            int sampleRate, int channelMask, int audioFormat,
             int buffSizeInBytes, int mode, int[] sessionId);
 
     private native final void native_finalize();
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/ClosedCaptionRenderer.java b/media/java/android/media/ClosedCaptionRenderer.java
new file mode 100644
index 0000000..ec33c5c
--- /dev/null
+++ b/media/java/android/media/ClosedCaptionRenderer.java
@@ -0,0 +1,1464 @@
+/*
+ * 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.media;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.text.style.UpdateAppearance;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Vector;
+
+/** @hide */
+public class ClosedCaptionRenderer extends SubtitleController.Renderer {
+    private final Context mContext;
+    private ClosedCaptionWidget mRenderingWidget;
+
+    public ClosedCaptionRenderer(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean supports(MediaFormat format) {
+        if (format.containsKey(MediaFormat.KEY_MIME)) {
+            return format.getString(MediaFormat.KEY_MIME).equals(
+                    MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608);
+        }
+        return false;
+    }
+
+    @Override
+    public SubtitleTrack createTrack(MediaFormat format) {
+        if (mRenderingWidget == null) {
+            mRenderingWidget = new ClosedCaptionWidget(mContext);
+        }
+        return new ClosedCaptionTrack(mRenderingWidget, format);
+    }
+}
+
+/** @hide */
+class ClosedCaptionTrack extends SubtitleTrack {
+    private final ClosedCaptionWidget mRenderingWidget;
+    private final CCParser mCCParser;
+
+    ClosedCaptionTrack(ClosedCaptionWidget renderingWidget, MediaFormat format) {
+        super(format);
+
+        mRenderingWidget = renderingWidget;
+        mCCParser = new CCParser(renderingWidget);
+    }
+
+    @Override
+    public void onData(byte[] data, boolean eos, long runID) {
+        mCCParser.parse(data);
+    }
+
+    @Override
+    public RenderingWidget getRenderingWidget() {
+        return mRenderingWidget;
+    }
+
+    @Override
+    public void updateView(Vector<Cue> activeCues) {
+        // Overriding with NO-OP, CC rendering by-passes this
+    }
+}
+
+/**
+ * @hide
+ *
+ * CCParser processes CEA-608 closed caption data.
+ *
+ * It calls back into OnDisplayChangedListener upon
+ * display change with styled text for rendering.
+ *
+ */
+class CCParser {
+    public static final int MAX_ROWS = 15;
+    public static final int MAX_COLS = 32;
+
+    private static final String TAG = "CCParser";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int INVALID = -1;
+
+    // EIA-CEA-608: Table 70 - Control Codes
+    private static final int RCL = 0x20;
+    private static final int BS  = 0x21;
+    private static final int AOF = 0x22;
+    private static final int AON = 0x23;
+    private static final int DER = 0x24;
+    private static final int RU2 = 0x25;
+    private static final int RU3 = 0x26;
+    private static final int RU4 = 0x27;
+    private static final int FON = 0x28;
+    private static final int RDC = 0x29;
+    private static final int TR  = 0x2a;
+    private static final int RTD = 0x2b;
+    private static final int EDM = 0x2c;
+    private static final int CR  = 0x2d;
+    private static final int ENM = 0x2e;
+    private static final int EOC = 0x2f;
+
+    // Transparent Space
+    private static final char TS = '\u00A0';
+
+    // Captioning Modes
+    private static final int MODE_UNKNOWN = 0;
+    private static final int MODE_PAINT_ON = 1;
+    private static final int MODE_ROLL_UP = 2;
+    private static final int MODE_POP_ON = 3;
+    private static final int MODE_TEXT = 4;
+
+    private final DisplayListener mListener;
+
+    private int mMode = MODE_PAINT_ON;
+    private int mRollUpSize = 4;
+
+    private CCMemory mDisplay = new CCMemory();
+    private CCMemory mNonDisplay = new CCMemory();
+    private CCMemory mTextMem = new CCMemory();
+
+    CCParser(DisplayListener listener) {
+        mListener = listener;
+    }
+
+    void parse(byte[] data) {
+        CCData[] ccData = CCData.fromByteArray(data);
+
+        for (int i = 0; i < ccData.length; i++) {
+            if (DEBUG) {
+                Log.d(TAG, ccData[i].toString());
+            }
+
+            if (handleCtrlCode(ccData[i])
+                    || handleTabOffsets(ccData[i])
+                    || handlePACCode(ccData[i])
+                    || handleMidRowCode(ccData[i])) {
+                continue;
+            }
+
+            handleDisplayableChars(ccData[i]);
+        }
+    }
+
+    interface DisplayListener {
+        public void onDisplayChanged(SpannableStringBuilder[] styledTexts);
+        public CaptionStyle getCaptionStyle();
+    }
+
+    private CCMemory getMemory() {
+        // get the CC memory to operate on for current mode
+        switch (mMode) {
+        case MODE_POP_ON:
+            return mNonDisplay;
+        case MODE_TEXT:
+            // TODO(chz): support only caption mode for now,
+            // in text mode, dump everything to text mem.
+            return mTextMem;
+        case MODE_PAINT_ON:
+        case MODE_ROLL_UP:
+            return mDisplay;
+        default:
+            Log.w(TAG, "unrecoginized mode: " + mMode);
+        }
+        return mDisplay;
+    }
+
+    private boolean handleDisplayableChars(CCData ccData) {
+        if (!ccData.isDisplayableChar()) {
+            return false;
+        }
+
+        // Extended char includes 1 automatic backspace
+        if (ccData.isExtendedChar()) {
+            getMemory().bs();
+        }
+
+        getMemory().writeText(ccData.getDisplayText());
+
+        if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
+            updateDisplay();
+        }
+
+        return true;
+    }
+
+    private boolean handleMidRowCode(CCData ccData) {
+        StyleCode m = ccData.getMidRow();
+        if (m != null) {
+            getMemory().writeMidRowCode(m);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handlePACCode(CCData ccData) {
+        PAC pac = ccData.getPAC();
+
+        if (pac != null) {
+            if (mMode == MODE_ROLL_UP) {
+                getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
+            }
+            getMemory().writePAC(pac);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean handleTabOffsets(CCData ccData) {
+        int tabs = ccData.getTabOffset();
+
+        if (tabs > 0) {
+            getMemory().tab(tabs);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean handleCtrlCode(CCData ccData) {
+        int ctrlCode = ccData.getCtrlCode();
+        switch(ctrlCode) {
+        case RCL:
+            // select pop-on style
+            mMode = MODE_POP_ON;
+            break;
+        case BS:
+            getMemory().bs();
+            break;
+        case DER:
+            getMemory().der();
+            break;
+        case RU2:
+        case RU3:
+        case RU4:
+            mRollUpSize = (ctrlCode - 0x23);
+            // erase memory if currently in other style
+            if (mMode != MODE_ROLL_UP) {
+                mDisplay.erase();
+                mNonDisplay.erase();
+            }
+            // select roll-up style
+            mMode = MODE_ROLL_UP;
+            break;
+        case FON:
+            Log.i(TAG, "Flash On");
+            break;
+        case RDC:
+            // select paint-on style
+            mMode = MODE_PAINT_ON;
+            break;
+        case TR:
+            mMode = MODE_TEXT;
+            mTextMem.erase();
+            break;
+        case RTD:
+            mMode = MODE_TEXT;
+            break;
+        case EDM:
+            // erase display memory
+            mDisplay.erase();
+            updateDisplay();
+            break;
+        case CR:
+            if (mMode == MODE_ROLL_UP) {
+                getMemory().rollUp(mRollUpSize);
+            } else {
+                getMemory().cr();
+            }
+            if (mMode == MODE_ROLL_UP) {
+                updateDisplay();
+            }
+            break;
+        case ENM:
+            // erase non-display memory
+            mNonDisplay.erase();
+            break;
+        case EOC:
+            // swap display/non-display memory
+            swapMemory();
+            // switch to pop-on style
+            mMode = MODE_POP_ON;
+            updateDisplay();
+            break;
+        case INVALID:
+        default:
+            // not handled
+            return false;
+        }
+
+        // handled
+        return true;
+    }
+
+    private void updateDisplay() {
+        if (mListener != null) {
+            CaptionStyle captionStyle = mListener.getCaptionStyle();
+            mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
+        }
+    }
+
+    private void swapMemory() {
+        CCMemory temp = mDisplay;
+        mDisplay = mNonDisplay;
+        mNonDisplay = temp;
+    }
+
+    private static class StyleCode {
+        static final int COLOR_WHITE = 0;
+        static final int COLOR_GREEN = 1;
+        static final int COLOR_BLUE = 2;
+        static final int COLOR_CYAN = 3;
+        static final int COLOR_RED = 4;
+        static final int COLOR_YELLOW = 5;
+        static final int COLOR_MAGENTA = 6;
+        static final int COLOR_INVALID = 7;
+
+        static final int STYLE_ITALICS   = 0x00000001;
+        static final int STYLE_UNDERLINE = 0x00000002;
+
+        static final String[] mColorMap = {
+            "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
+        };
+
+        final int mStyle;
+        final int mColor;
+
+        static StyleCode fromByte(byte data2) {
+            int style = 0;
+            int color = (data2 >> 1) & 0x7;
+
+            if ((data2 & 0x1) != 0) {
+                style |= STYLE_UNDERLINE;
+            }
+
+            if (color == COLOR_INVALID) {
+                // WHITE ITALICS
+                color = COLOR_WHITE;
+                style |= STYLE_ITALICS;
+            }
+
+            return new StyleCode(style, color);
+        }
+
+        StyleCode(int style, int color) {
+            mStyle = style;
+            mColor = color;
+        }
+
+        boolean isItalics() {
+            return (mStyle & STYLE_ITALICS) != 0;
+        }
+
+        boolean isUnderline() {
+            return (mStyle & STYLE_UNDERLINE) != 0;
+        }
+
+        int getColor() {
+            return mColor;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            str.append("{");
+            str.append(mColorMap[mColor]);
+            if ((mStyle & STYLE_ITALICS) != 0) {
+                str.append(", ITALICS");
+            }
+            if ((mStyle & STYLE_UNDERLINE) != 0) {
+                str.append(", UNDERLINE");
+            }
+            str.append("}");
+
+            return str.toString();
+        }
+    }
+
+    private static class PAC extends StyleCode {
+        final int mRow;
+        final int mCol;
+
+        static PAC fromBytes(byte data1, byte data2) {
+            int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
+            int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
+            int style = 0;
+            if ((data2 & 1) != 0) {
+                style |= STYLE_UNDERLINE;
+            }
+            if ((data2 & 0x10) != 0) {
+                // indent code
+                int indent = (data2 >> 1) & 0x7;
+                return new PAC(row, indent * 4, style, COLOR_WHITE);
+            } else {
+                // style code
+                int color = (data2 >> 1) & 0x7;
+
+                if (color == COLOR_INVALID) {
+                    // WHITE ITALICS
+                    color = COLOR_WHITE;
+                    style |= STYLE_ITALICS;
+                }
+                return new PAC(row, -1, style, color);
+            }
+        }
+
+        PAC(int row, int col, int style, int color) {
+            super(style, color);
+            mRow = row;
+            mCol = col;
+        }
+
+        boolean isIndentPAC() {
+            return (mCol >= 0);
+        }
+
+        int getRow() {
+            return mRow;
+        }
+
+        int getCol() {
+            return mCol;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{%d, %d}, %s",
+                    mRow, mCol, super.toString());
+        }
+    }
+
+    /* CCLineBuilder keeps track of displayable chars, as well as
+     * MidRow styles and PACs, for a single line of CC memory.
+     *
+     * It generates styled text via getStyledText() method.
+     */
+    private static class CCLineBuilder {
+        private final StringBuilder mDisplayChars;
+        private final StyleCode[] mMidRowStyles;
+        private final StyleCode[] mPACStyles;
+
+        CCLineBuilder(String str) {
+            mDisplayChars = new StringBuilder(str);
+            mMidRowStyles = new StyleCode[mDisplayChars.length()];
+            mPACStyles = new StyleCode[mDisplayChars.length()];
+        }
+
+        void setCharAt(int index, char ch) {
+            mDisplayChars.setCharAt(index, ch);
+            mMidRowStyles[index] = null;
+        }
+
+        void setMidRowAt(int index, StyleCode m) {
+            mDisplayChars.setCharAt(index, ' ');
+            mMidRowStyles[index] = m;
+        }
+
+        void setPACAt(int index, PAC pac) {
+            mPACStyles[index] = pac;
+        }
+
+        char charAt(int index) {
+            return mDisplayChars.charAt(index);
+        }
+
+        int length() {
+            return mDisplayChars.length();
+        }
+
+        void applyStyleSpan(
+                SpannableStringBuilder styledText,
+                StyleCode s, int start, int end) {
+            if (s.isItalics()) {
+                styledText.setSpan(
+                        new StyleSpan(android.graphics.Typeface.ITALIC),
+                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            if (s.isUnderline()) {
+                styledText.setSpan(
+                        new UnderlineSpan(),
+                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+
+        SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
+            SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
+            int start = -1, next = 0;
+            int styleStart = -1;
+            StyleCode curStyle = null;
+            while (next < mDisplayChars.length()) {
+                StyleCode newStyle = null;
+                if (mMidRowStyles[next] != null) {
+                    // apply mid-row style change
+                    newStyle = mMidRowStyles[next];
+                } else if (mPACStyles[next] != null
+                    && (styleStart < 0 || start < 0)) {
+                    // apply PAC style change, only if:
+                    // 1. no style set, or
+                    // 2. style set, but prev char is none-displayable
+                    newStyle = mPACStyles[next];
+                }
+                if (newStyle != null) {
+                    curStyle = newStyle;
+                    if (styleStart >= 0 && start >= 0) {
+                        applyStyleSpan(styledText, newStyle, styleStart, next);
+                    }
+                    styleStart = next;
+                }
+
+                if (mDisplayChars.charAt(next) != TS) {
+                    if (start < 0) {
+                        start = next;
+                    }
+                } else if (start >= 0) {
+                    int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
+                    int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
+                    styledText.setSpan(
+                            new MutableBackgroundColorSpan(captionStyle.backgroundColor),
+                            expandedStart, expandedEnd,
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    if (styleStart >= 0) {
+                        applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
+                    }
+                    start = -1;
+                }
+                next++;
+            }
+
+            return styledText;
+        }
+    }
+
+    /*
+     * CCMemory models a console-style display.
+     */
+    private static class CCMemory {
+        private final String mBlankLine;
+        private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
+        private int mRow;
+        private int mCol;
+
+        CCMemory() {
+            char[] blank = new char[MAX_COLS + 2];
+            Arrays.fill(blank, TS);
+            mBlankLine = new String(blank);
+        }
+
+        void erase() {
+            // erase all lines
+            for (int i = 0; i < mLines.length; i++) {
+                mLines[i] = null;
+            }
+            mRow = MAX_ROWS;
+            mCol = 1;
+        }
+
+        void der() {
+            if (mLines[mRow] != null) {
+                for (int i = 0; i < mCol; i++) {
+                    if (mLines[mRow].charAt(i) != TS) {
+                        for (int j = mCol; j < mLines[mRow].length(); j++) {
+                            mLines[j].setCharAt(j, TS);
+                        }
+                        return;
+                    }
+                }
+                mLines[mRow] = null;
+            }
+        }
+
+        void tab(int tabs) {
+            moveCursorByCol(tabs);
+        }
+
+        void bs() {
+            moveCursorByCol(-1);
+            if (mLines[mRow] != null) {
+                mLines[mRow].setCharAt(mCol, TS);
+                if (mCol == MAX_COLS - 1) {
+                    // Spec recommendation:
+                    // if cursor was at col 32, move cursor
+                    // back to col 31 and erase both col 31&32
+                    mLines[mRow].setCharAt(MAX_COLS, TS);
+                }
+            }
+        }
+
+        void cr() {
+            moveCursorTo(mRow + 1, 1);
+        }
+
+        void rollUp(int windowSize) {
+            int i;
+            for (i = 0; i <= mRow - windowSize; i++) {
+                mLines[i] = null;
+            }
+            int startRow = mRow - windowSize + 1;
+            if (startRow < 1) {
+                startRow = 1;
+            }
+            for (i = startRow; i < mRow; i++) {
+                mLines[i] = mLines[i + 1];
+            }
+            for (i = mRow; i < mLines.length; i++) {
+                // clear base row
+                mLines[i] = null;
+            }
+            // default to col 1, in case PAC is not sent
+            mCol = 1;
+        }
+
+        void writeText(String text) {
+            for (int i = 0; i < text.length(); i++) {
+                getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
+                moveCursorByCol(1);
+            }
+        }
+
+        void writeMidRowCode(StyleCode m) {
+            getLineBuffer(mRow).setMidRowAt(mCol, m);
+            moveCursorByCol(1);
+        }
+
+        void writePAC(PAC pac) {
+            if (pac.isIndentPAC()) {
+                moveCursorTo(pac.getRow(), pac.getCol());
+            } else {
+                moveCursorToRow(pac.getRow());
+            }
+            getLineBuffer(mRow).setPACAt(mCol, pac);
+        }
+
+        SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
+            ArrayList<SpannableStringBuilder> rows =
+                    new ArrayList<SpannableStringBuilder>(MAX_ROWS);
+            for (int i = 1; i <= MAX_ROWS; i++) {
+                rows.add(mLines[i] != null ?
+                        mLines[i].getStyledText(captionStyle) : null);
+            }
+            return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
+        }
+
+        private static int clamp(int x, int min, int max) {
+            return x < min ? min : (x > max ? max : x);
+        }
+
+        private void moveCursorTo(int row, int col) {
+            mRow = clamp(row, 1, MAX_ROWS);
+            mCol = clamp(col, 1, MAX_COLS);
+        }
+
+        private void moveCursorToRow(int row) {
+            mRow = clamp(row, 1, MAX_ROWS);
+        }
+
+        private void moveCursorByCol(int col) {
+            mCol = clamp(mCol + col, 1, MAX_COLS);
+        }
+
+        private void moveBaselineTo(int baseRow, int windowSize) {
+            if (mRow == baseRow) {
+                return;
+            }
+            int actualWindowSize = windowSize;
+            if (baseRow < actualWindowSize) {
+                actualWindowSize = baseRow;
+            }
+            if (mRow < actualWindowSize) {
+                actualWindowSize = mRow;
+            }
+
+            int i;
+            if (baseRow < mRow) {
+                // copy from bottom to top row
+                for (i = actualWindowSize - 1; i >= 0; i--) {
+                    mLines[baseRow - i] = mLines[mRow - i];
+                }
+            } else {
+                // copy from top to bottom row
+                for (i = 0; i < actualWindowSize; i++) {
+                    mLines[baseRow - i] = mLines[mRow - i];
+                }
+            }
+            // clear rest of the rows
+            for (i = 0; i <= baseRow - windowSize; i++) {
+                mLines[i] = null;
+            }
+            for (i = baseRow + 1; i < mLines.length; i++) {
+                mLines[i] = null;
+            }
+        }
+
+        private CCLineBuilder getLineBuffer(int row) {
+            if (mLines[row] == null) {
+                mLines[row] = new CCLineBuilder(mBlankLine);
+            }
+            return mLines[row];
+        }
+    }
+
+    /*
+     * CCData parses the raw CC byte pair into displayable chars,
+     * misc control codes, Mid-Row or Preamble Address Codes.
+     */
+    private static class CCData {
+        private final byte mType;
+        private final byte mData1;
+        private final byte mData2;
+
+        private static final String[] mCtrlCodeMap = {
+            "RCL", "BS" , "AOF", "AON",
+            "DER", "RU2", "RU3", "RU4",
+            "FON", "RDC", "TR" , "RTD",
+            "EDM", "CR" , "ENM", "EOC",
+        };
+
+        private static final String[] mSpecialCharMap = {
+            "\u00AE",
+            "\u00B0",
+            "\u00BD",
+            "\u00BF",
+            "\u2122",
+            "\u00A2",
+            "\u00A3",
+            "\u266A", // Eighth note
+            "\u00E0",
+            "\u00A0", // Transparent space
+            "\u00E8",
+            "\u00E2",
+            "\u00EA",
+            "\u00EE",
+            "\u00F4",
+            "\u00FB",
+        };
+
+        private static final String[] mSpanishCharMap = {
+            // Spanish and misc chars
+            "\u00C1", // A
+            "\u00C9", // E
+            "\u00D3", // I
+            "\u00DA", // O
+            "\u00DC", // U
+            "\u00FC", // u
+            "\u2018", // opening single quote
+            "\u00A1", // inverted exclamation mark
+            "*",
+            "'",
+            "\u2014", // em dash
+            "\u00A9", // Copyright
+            "\u2120", // Servicemark
+            "\u2022", // round bullet
+            "\u201C", // opening double quote
+            "\u201D", // closing double quote
+            // French
+            "\u00C0",
+            "\u00C2",
+            "\u00C7",
+            "\u00C8",
+            "\u00CA",
+            "\u00CB",
+            "\u00EB",
+            "\u00CE",
+            "\u00CF",
+            "\u00EF",
+            "\u00D4",
+            "\u00D9",
+            "\u00F9",
+            "\u00DB",
+            "\u00AB",
+            "\u00BB"
+        };
+
+        private static final String[] mProtugueseCharMap = {
+            // Portuguese
+            "\u00C3",
+            "\u00E3",
+            "\u00CD",
+            "\u00CC",
+            "\u00EC",
+            "\u00D2",
+            "\u00F2",
+            "\u00D5",
+            "\u00F5",
+            "{",
+            "}",
+            "\\",
+            "^",
+            "_",
+            "|",
+            "~",
+            // German and misc chars
+            "\u00C4",
+            "\u00E4",
+            "\u00D6",
+            "\u00F6",
+            "\u00DF",
+            "\u00A5",
+            "\u00A4",
+            "\u2502", // vertical bar
+            "\u00C5",
+            "\u00E5",
+            "\u00D8",
+            "\u00F8",
+            "\u250C", // top-left corner
+            "\u2510", // top-right corner
+            "\u2514", // lower-left corner
+            "\u2518", // lower-right corner
+        };
+
+        static CCData[] fromByteArray(byte[] data) {
+            CCData[] ccData = new CCData[data.length / 3];
+
+            for (int i = 0; i < ccData.length; i++) {
+                ccData[i] = new CCData(
+                        data[i * 3],
+                        data[i * 3 + 1],
+                        data[i * 3 + 2]);
+            }
+
+            return ccData;
+        }
+
+        CCData(byte type, byte data1, byte data2) {
+            mType = type;
+            mData1 = data1;
+            mData2 = data2;
+        }
+
+        int getCtrlCode() {
+            if ((mData1 == 0x14 || mData1 == 0x1c)
+                    && mData2 >= 0x20 && mData2 <= 0x2f) {
+                return mData2;
+            }
+            return INVALID;
+        }
+
+        StyleCode getMidRow() {
+            // only support standard Mid-row codes, ignore
+            // optional background/foreground mid-row codes
+            if ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x20 && mData2 <= 0x2f) {
+                return StyleCode.fromByte(mData2);
+            }
+            return null;
+        }
+
+        PAC getPAC() {
+            if ((mData1 & 0x70) == 0x10
+                    && (mData2 & 0x40) == 0x40
+                    && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
+                return PAC.fromBytes(mData1, mData2);
+            }
+            return null;
+        }
+
+        int getTabOffset() {
+            if ((mData1 == 0x17 || mData1 == 0x1f)
+                    && mData2 >= 0x21 && mData2 <= 0x23) {
+                return mData2 & 0x3;
+            }
+            return 0;
+        }
+
+        boolean isDisplayableChar() {
+            return isBasicChar() || isSpecialChar() || isExtendedChar();
+        }
+
+        String getDisplayText() {
+            String str = getBasicChars();
+
+            if (str == null) {
+                str =  getSpecialChar();
+
+                if (str == null) {
+                    str = getExtendedChar();
+                }
+            }
+
+            return str;
+        }
+
+        private String ctrlCodeToString(int ctrlCode) {
+            return mCtrlCodeMap[ctrlCode - 0x20];
+        }
+
+        private boolean isBasicChar() {
+            return mData1 >= 0x20 && mData1 <= 0x7f;
+        }
+
+        private boolean isSpecialChar() {
+            return ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x30 && mData2 <= 0x3f);
+        }
+
+        private boolean isExtendedChar() {
+            return ((mData1 == 0x12 || mData1 == 0x1A
+                    || mData1 == 0x13 || mData1 == 0x1B)
+                    && mData2 >= 0x20 && mData2 <= 0x3f);
+        }
+
+        private char getBasicChar(byte data) {
+            char c;
+            // replace the non-ASCII ones
+            switch (data) {
+                case 0x2A: c = '\u00E1'; break;
+                case 0x5C: c = '\u00E9'; break;
+                case 0x5E: c = '\u00ED'; break;
+                case 0x5F: c = '\u00F3'; break;
+                case 0x60: c = '\u00FA'; break;
+                case 0x7B: c = '\u00E7'; break;
+                case 0x7C: c = '\u00F7'; break;
+                case 0x7D: c = '\u00D1'; break;
+                case 0x7E: c = '\u00F1'; break;
+                case 0x7F: c = '\u2588'; break; // Full block
+                default: c = (char) data; break;
+            }
+            return c;
+        }
+
+        private String getBasicChars() {
+            if (mData1 >= 0x20 && mData1 <= 0x7f) {
+                StringBuilder builder = new StringBuilder(2);
+                builder.append(getBasicChar(mData1));
+                if (mData2 >= 0x20 && mData2 <= 0x7f) {
+                    builder.append(getBasicChar(mData2));
+                }
+                return builder.toString();
+            }
+
+            return null;
+        }
+
+        private String getSpecialChar() {
+            if ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x30 && mData2 <= 0x3f) {
+                return mSpecialCharMap[mData2 - 0x30];
+            }
+
+            return null;
+        }
+
+        private String getExtendedChar() {
+            if ((mData1 == 0x12 || mData1 == 0x1A)
+                    && mData2 >= 0x20 && mData2 <= 0x3f){
+                // 1 Spanish/French char
+                return mSpanishCharMap[mData2 - 0x20];
+            } else if ((mData1 == 0x13 || mData1 == 0x1B)
+                    && mData2 >= 0x20 && mData2 <= 0x3f){
+                // 1 Portuguese/German/Danish char
+                return mProtugueseCharMap[mData2 - 0x20];
+            }
+
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            String str;
+
+            if (mData1 < 0x10 && mData2 < 0x10) {
+                // Null Pad, ignore
+                return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
+            }
+
+            int ctrlCode = getCtrlCode();
+            if (ctrlCode != INVALID) {
+                return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
+            }
+
+            int tabOffset = getTabOffset();
+            if (tabOffset > 0) {
+                return String.format("[%d]Tab%d", mType, tabOffset);
+            }
+
+            PAC pac = getPAC();
+            if (pac != null) {
+                return String.format("[%d]PAC: %s", mType, pac.toString());
+            }
+
+            StyleCode m = getMidRow();
+            if (m != null) {
+                return String.format("[%d]Mid-row: %s", mType, m.toString());
+            }
+
+            if (isDisplayableChar()) {
+                return String.format("[%d]Displayable: %s (%02x %02x)",
+                        mType, getDisplayText(), mData1, mData2);
+            }
+
+            return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
+        }
+    }
+}
+
+/**
+ * @hide
+ *
+ * MutableBackgroundColorSpan
+ *
+ * This is a mutable version of BackgroundSpan to facilitate text
+ * rendering with edge styles.
+ *
+ */
+class MutableBackgroundColorSpan extends CharacterStyle
+        implements UpdateAppearance, ParcelableSpan {
+    private int mColor;
+
+    public MutableBackgroundColorSpan(int color) {
+        mColor = color;
+    }
+    public MutableBackgroundColorSpan(Parcel src) {
+        mColor = src.readInt();
+    }
+    public void setBackgroundColor(int color) {
+        mColor = color;
+    }
+    public int getBackgroundColor() {
+        return mColor;
+    }
+    @Override
+    public int getSpanTypeId() {
+        return TextUtils.BACKGROUND_COLOR_SPAN;
+    }
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mColor);
+    }
+    @Override
+    public void updateDrawState(TextPaint ds) {
+        ds.bgColor = mColor;
+    }
+}
+
+/**
+ * Widget capable of rendering CEA-608 closed captions.
+ *
+ * @hide
+ */
+class ClosedCaptionWidget extends ViewGroup implements
+        SubtitleTrack.RenderingWidget,
+        CCParser.DisplayListener {
+    private static final String TAG = "ClosedCaptionWidget";
+
+    private static final Rect mTextBounds = new Rect();
+    private static final String mDummyText = "1234567890123456789012345678901234";
+    private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
+
+    /** Captioning manager, used to obtain and track caption properties. */
+    private final CaptioningManager mManager;
+
+    /** Callback for rendering changes. */
+    private OnChangedListener mListener;
+
+    /** Current caption style. */
+    private CaptionStyle mCaptionStyle;
+
+    /* Closed caption layout. */
+    private CCLayout mClosedCaptionLayout;
+
+    /** Whether a caption style change listener is registered. */
+    private boolean mHasChangeListener;
+
+    public ClosedCaptionWidget(Context context) {
+        this(context, null);
+    }
+
+    public ClosedCaptionWidget(Context context, AttributeSet attrs) {
+        this(context, null, 0);
+    }
+
+    public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        // Cannot render text over video when layer type is hardware.
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+        mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
+
+        mClosedCaptionLayout = new CCLayout(context);
+        mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+        addView(mClosedCaptionLayout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+        requestLayout();
+    }
+
+    @Override
+    public void setOnChangedListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+        measure(widthSpec, heightSpec);
+        layout(0, 0, width, height);
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.GONE);
+        }
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
+        mClosedCaptionLayout.update(styledTexts);
+
+        if (mListener != null) {
+            mListener.onChanged(this);
+        }
+    }
+
+    @Override
+    public CaptionStyle getCaptionStyle() {
+        return mCaptionStyle;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mClosedCaptionLayout.measure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mClosedCaptionLayout.layout(l, t, r, b);
+    }
+
+    /**
+     * Manages whether this renderer is listening for caption style changes.
+     */
+    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
+        @Override
+        public void onUserStyleChanged(CaptionStyle userStyle) {
+            mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
+            mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+        }
+    };
+
+    private void manageChangeListener() {
+        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
+        if (mHasChangeListener != needsListener) {
+            mHasChangeListener = needsListener;
+
+            if (needsListener) {
+                mManager.addCaptioningChangeListener(mCaptioningListener);
+            } else {
+                mManager.removeCaptioningChangeListener(mCaptioningListener);
+            }
+        }
+    }
+
+    private static class CCLineBox extends TextView {
+        private static final float FONT_PADDING_RATIO = 0.75f;
+        private static final float EDGE_OUTLINE_RATIO = 0.1f;
+        private static final float EDGE_SHADOW_RATIO = 0.05f;
+        private float mOutlineWidth;
+        private float mShadowRadius;
+        private float mShadowOffset;
+
+        private int mTextColor = Color.WHITE;
+        private int mBgColor = Color.BLACK;
+        private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE;
+        private int mEdgeColor = Color.TRANSPARENT;
+
+        CCLineBox(Context context) {
+            super(context);
+            setGravity(Gravity.CENTER);
+            setBackgroundColor(Color.TRANSPARENT);
+            setTextColor(Color.WHITE);
+            setTypeface(Typeface.MONOSPACE);
+            setVisibility(View.INVISIBLE);
+
+            final Resources res = getContext().getResources();
+
+            // get the default (will be updated later during measure)
+            mOutlineWidth = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_outline_width);
+            mShadowRadius = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_shadow_radius);
+            mShadowOffset = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_shadow_offset);
+        }
+
+        void setCaptionStyle(CaptionStyle captionStyle) {
+            mTextColor = captionStyle.foregroundColor;
+            mBgColor = captionStyle.backgroundColor;
+            mEdgeType = captionStyle.edgeType;
+            mEdgeColor = captionStyle.edgeColor;
+
+            setTextColor(mTextColor);
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+                setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
+            } else {
+                setShadowLayer(0, 0, 0, 0);
+            }
+            invalidate();
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            float fontSize = MeasureSpec.getSize(heightMeasureSpec)
+                    * FONT_PADDING_RATIO;
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
+
+            mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
+            mShadowRadius = EDGE_SHADOW_RATIO * fontSize + 1.0f;;
+            mShadowOffset = mShadowRadius;
+
+            // set font scale in the X direction to match the required width
+            setScaleX(1.0f);
+            getPaint().getTextBounds(mDummyText, 0, mDummyText.length(), mTextBounds);
+            float actualTextWidth = mTextBounds.width();
+            float requiredTextWidth = MeasureSpec.getSize(widthMeasureSpec);
+            setScaleX(requiredTextWidth / actualTextWidth);
+
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
+        protected void onDraw(Canvas c) {
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_UNSPECIFIED
+                    || mEdgeType == CaptionStyle.EDGE_TYPE_NONE
+                    || mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+                // these edge styles don't require a second pass
+                super.onDraw(c);
+                return;
+            }
+
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
+                drawEdgeOutline(c);
+            } else {
+                // Raised or depressed
+                drawEdgeRaisedOrDepressed(c);
+            }
+        }
+
+        private void drawEdgeOutline(Canvas c) {
+            TextPaint textPaint = getPaint();
+
+            Paint.Style previousStyle = textPaint.getStyle();
+            Paint.Join previousJoin = textPaint.getStrokeJoin();
+            float previousWidth = textPaint.getStrokeWidth();
+
+            setTextColor(mEdgeColor);
+            textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+            textPaint.setStrokeJoin(Paint.Join.ROUND);
+            textPaint.setStrokeWidth(mOutlineWidth);
+
+            // Draw outline and background only.
+            super.onDraw(c);
+
+            // Restore original settings.
+            setTextColor(mTextColor);
+            textPaint.setStyle(previousStyle);
+            textPaint.setStrokeJoin(previousJoin);
+            textPaint.setStrokeWidth(previousWidth);
+
+            // Remove the background.
+            setBackgroundSpans(Color.TRANSPARENT);
+            // Draw foreground only.
+            super.onDraw(c);
+            // Restore the background.
+            setBackgroundSpans(mBgColor);
+        }
+
+        private void drawEdgeRaisedOrDepressed(Canvas c) {
+            TextPaint textPaint = getPaint();
+
+            Paint.Style previousStyle = textPaint.getStyle();
+            textPaint.setStyle(Paint.Style.FILL);
+
+            final boolean raised = mEdgeType == CaptionStyle.EDGE_TYPE_RAISED;
+            final int colorUp = raised ? Color.WHITE : mEdgeColor;
+            final int colorDown = raised ? mEdgeColor : Color.WHITE;
+            final float offset = mShadowRadius / 2f;
+
+            // Draw background and text with shadow up
+            setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
+            super.onDraw(c);
+
+            // Remove the background.
+            setBackgroundSpans(Color.TRANSPARENT);
+
+            // Draw text with shadow down
+            setShadowLayer(mShadowRadius, +offset, +offset, colorDown);
+            super.onDraw(c);
+
+            // Restore settings
+            textPaint.setStyle(previousStyle);
+
+            // Restore the background.
+            setBackgroundSpans(mBgColor);
+        }
+
+        private void setBackgroundSpans(int color) {
+            CharSequence text = getText();
+            if (text instanceof Spannable) {
+                Spannable spannable = (Spannable) text;
+                MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
+                        0, spannable.length(), MutableBackgroundColorSpan.class);
+                for (int i = 0; i < bgSpans.length; i++) {
+                    bgSpans[i].setBackgroundColor(color);
+                }
+            }
+        }
+    }
+
+    private static class CCLayout extends LinearLayout {
+        private static final int MAX_ROWS = CCParser.MAX_ROWS;
+        private static final float SAFE_AREA_RATIO = 0.9f;
+
+        private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
+
+        CCLayout(Context context) {
+            super(context);
+            setGravity(Gravity.START);
+            setOrientation(LinearLayout.VERTICAL);
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i] = new CCLineBox(getContext());
+                addView(mLineBoxes[i], LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            }
+        }
+
+        void setCaptionStyle(CaptionStyle captionStyle) {
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].setCaptionStyle(captionStyle);
+            }
+        }
+
+        void update(SpannableStringBuilder[] textBuffer) {
+            for (int i = 0; i < MAX_ROWS; i++) {
+                if (textBuffer[i] != null) {
+                    mLineBoxes[i].setText(textBuffer[i], TextView.BufferType.SPANNABLE);
+                    mLineBoxes[i].setVisibility(View.VISIBLE);
+                } else {
+                    mLineBoxes[i].setVisibility(View.INVISIBLE);
+                }
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            int safeWidth = getMeasuredWidth();
+            int safeHeight = getMeasuredHeight();
+
+            // CEA-608 assumes 4:3 video
+            if (safeWidth * 3 >= safeHeight * 4) {
+                safeWidth = safeHeight * 4 / 3;
+            } else {
+                safeHeight = safeWidth * 3 / 4;
+            }
+            safeWidth *= SAFE_AREA_RATIO;
+            safeHeight *= SAFE_AREA_RATIO;
+
+            int lineHeight = safeHeight / MAX_ROWS;
+            int lineHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    lineHeight, MeasureSpec.EXACTLY);
+            int lineWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    safeWidth, MeasureSpec.EXACTLY);
+
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].measure(lineWidthMeasureSpec, lineHeightMeasureSpec);
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            // safe caption area
+            int viewPortWidth = r - l;
+            int viewPortHeight = b - t;
+            int safeWidth, safeHeight;
+            // CEA-608 assumes 4:3 video
+            if (viewPortWidth * 3 >= viewPortHeight * 4) {
+                safeWidth = viewPortHeight * 4 / 3;
+                safeHeight = viewPortHeight;
+            } else {
+                safeWidth = viewPortWidth;
+                safeHeight = viewPortWidth * 3 / 4;
+            }
+            safeWidth *= SAFE_AREA_RATIO;
+            safeHeight *= SAFE_AREA_RATIO;
+            int left = (viewPortWidth - safeWidth) / 2;
+            int top = (viewPortHeight - safeHeight) / 2;
+
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].layout(
+                        left,
+                        top + safeHeight * i / MAX_ROWS,
+                        left + safeWidth,
+                        top + safeHeight * (i + 1) / MAX_ROWS);
+            }
+        }
+    }
+};
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 30de4f9..4dcdd19 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -36,13 +36,8 @@
  */
 interface IAudioService {
 
-    void adjustVolume(int direction, int flags, String callingPackage);
-
     boolean isLocalOrRemoteMusicActive();
 
-    oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction,
-            String callingPackage);
-
     void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage);
 
@@ -62,7 +57,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();
 
@@ -78,6 +73,8 @@
 
     int getLastAudibleMasterVolume();
 
+    void setMicrophoneMute(boolean on, String callingPackage);
+
     void setRingerMode(int ringerMode);
 
     int getRingerMode();
@@ -125,15 +122,6 @@
 
     int getCurrentAudioFocus();
 
-    oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
-    void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
-
-           void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c, IBinder token);
-    oneway void unregisterMediaButtonIntent(in PendingIntent pi);
-
-    oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
-    oneway void unregisterMediaButtonEventReceiverForCalls();
-
     /**
      * Register an IRemoteControlDisplay.
      * Success of registration is subject to a check on
@@ -186,43 +174,9 @@
      */
     oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd,
             boolean wantsSync);
-    /**
-     * Request the user of a RemoteControlClient to seek to the given playback position.
-     * @param generationId the RemoteControlClient generation counter for which this request is
-     *         issued. Requests for an older generation than current one will be ignored.
-     * @param timeMs the time in ms to seek to, must be positive.
-     */
-     void setRemoteControlClientPlaybackPosition(int generationId, long timeMs);
-     /**
-      * Notify the user of a RemoteControlClient that it should update its metadata with the
-      * new value for the given key.
-      * @param generationId the RemoteControlClient generation counter for which this request is
-      *         issued. Requests for an older generation than current one will be ignored.
-      * @param key the metadata key for which a new value exists
-      * @param value the new metadata value
-      */
-     void updateRemoteControlClientMetadata(int generationId, int key, in Rating value);
-
-    /**
-     * Do not use directly, use instead
-     *     {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)}
-     */
-    int registerRemoteControlClient(in PendingIntent mediaIntent,
-            in IRemoteControlClient rcClient, in String callingPackageName);
-    /**
-     * Do not use directly, use instead
-     *     {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)}
-     */
-    oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
-            in IRemoteControlClient rcClient);
-
-    oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
-    void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed);
-           int  getRemoteStreamMaxVolume();
-           int  getRemoteStreamVolume();
-    oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo);
 
     void startBluetoothSco(IBinder cb, int targetSdkVersion);
+    void startBluetoothScoVirtualCall(IBinder cb);
     void stopBluetoothSco(IBinder cb);
 
     void forceVolumeControlStream(int streamType, IBinder cb);
@@ -232,7 +186,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/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
new file mode 100644
index 0000000..e4a4a42
--- /dev/null
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.media;
+
+import android.media.session.ISessionController;
+
+/**
+ * AIDL for the MediaSessionService to report interesting events on remote playback
+ * to a volume control dialog. See also IVolumeController for the AudioService half.
+ * TODO add in better support for multiple remote sessions.
+ * @hide
+ */
+oneway interface IRemoteVolumeController {
+    void remoteVolumeChanged(ISessionController session, int flags);
+    // sets the default session to use with the slider, replaces remoteSliderVisibility
+    // on IVolumeController
+    void updateRemoteController(ISessionController session);
+}
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index 35d7708..e3593a6 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -16,17 +16,12 @@
 
 package android.media;
 
-
 /**
- * AIDL for the AudioService to report interesting events to a remote volume control dialog.
+ * AIDL for the AudioService to report interesting events to a volume control
+ * dialog in another process.
  * @hide
  */
 oneway interface IVolumeController {
-    void hasNewRemotePlaybackInfo();
-
-    void remoteVolumeChanged(int streamType, int flags);
-
-    void remoteSliderVisibility(boolean visible);
 
     void displaySafeVolumeWarning(int flags);
 
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f258063..22db344 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -782,7 +782,7 @@
 
     private void postEventFromNative(
             int what, int arg1, int arg2, Object obj) {
-        if (mEventHandler != null) {
+        if (mEventHandler != null && mNotificationCallback != null) {
             Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
             mEventHandler.sendMessage(msg);
         }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 440653a..25f10d2 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -181,6 +181,27 @@
     }
 
     /**
+     * Thrown when an unrecoverable failure occurs during a MediaDrm operation.
+     * Extends java.lang.IllegalStateException with the addition of an error
+     * code that may be useful in diagnosing the failure.
+     */
+    public static final class MediaDrmStateException extends java.lang.IllegalStateException {
+        private final int mErrorCode;
+
+        public MediaDrmStateException(int errorCode, String detailMessage) {
+            super(detailMessage);
+            mErrorCode = errorCode;
+        }
+
+        /**
+         * Retrieve the associated error code
+         */
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
+    /**
      * Register a callback to be invoked when an event occurs
      *
      * @param listener the callback that will be run
@@ -304,7 +325,8 @@
      * @throws NotProvisionedException if provisioning is needed
      * @throws ResourceBusyException if required resources are in use
      */
-    public native byte[] openSession() throws NotProvisionedException;
+    public native byte[] openSession() throws NotProvisionedException,
+            ResourceBusyException;
 
     /**
      * Close a session on the MediaDrm object that was previously opened
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 1c73c05..6004ecf 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -77,7 +77,7 @@
     private final MediaEventHandler mEventHandler;
     private final Context mContext;
     private final ContentResolver mContentResolver;
-    private final VolumeController mVolumeController;
+    private final AudioService.VolumeController mVolumeController;
     private final BroadcastReceiver mReceiver = new PackageIntentsReceiver();
     private final AppOpsManager mAppOps;
     private final KeyguardManager mKeyguardManager;
@@ -85,7 +85,7 @@
     private final NotificationListenerObserver mNotifListenerObserver;
 
     protected MediaFocusControl(Looper looper, Context cntxt,
-            VolumeController volumeCtrl, AudioService as) {
+            AudioService.VolumeController volumeCtrl, AudioService as) {
         mEventHandler = new MediaEventHandler(looper);
         mContext = cntxt;
         mContentResolver = mContext.getContentResolver();
@@ -379,32 +379,11 @@
                     onReevaluateRemote();
                     break;
 
-                case MSG_RCC_NEW_PLAYBACK_INFO:
-                    onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */,
-                            ((Integer)msg.obj).intValue() /* value */);
-                    break;
-
                 case MSG_RCC_NEW_VOLUME_OBS:
                     onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
                             (IRemoteVolumeObserver)msg.obj /* rvo */);
                     break;
 
-                case MSG_RCC_NEW_PLAYBACK_STATE:
-                    onNewPlaybackStateForRcc(msg.arg1 /* rccId */,
-                            msg.arg2 /* state */,
-                            (PlayerRecord.RccPlaybackState)msg.obj /* newState */);
-                    break;
-
-                case MSG_RCC_SEEK_REQUEST:
-                    onSetRemoteControlClientPlaybackPosition(
-                            msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */);
-                    break;
-
-                case MSG_RCC_UPDATE_METADATA:
-                    onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/,
-                            (Rating) msg.obj /* value */);
-                    break;
-
                 case MSG_RCDISPLAY_INIT_INFO:
                     // msg.obj is guaranteed to be non null
                     onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
@@ -2003,217 +1982,6 @@
         }
     }
 
-    protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        // ignore position change requests if invalid generation ID
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if (mCurrentRcClientGen != generationId) {
-                    return;
-                }
-            }
-        }
-        // discard any unprocessed seek request in the message queue, and replace with latest
-        sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */,
-                0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */);
-    }
-
-    private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) {
-        if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId +
-                ", timeMs=" + timeMs + ")");
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) {
-                    // tell the current client to seek to the requested location
-                    try {
-                        mCurrentRcClient.seekTo(generationId, timeMs);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Current valid remote client is dead: "+e);
-                        mCurrentRcClient = null;
-                    }
-                }
-            }
-        }
-    }
-
-    protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) {
-        sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE,
-                genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */);
-    }
-
-    private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
-        if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
-                ", what=" + key + ",rating=" + value + ")");
-        synchronized(mPRStack) {
-            synchronized(mCurrentRcLock) {
-                if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
-                    try {
-                        switch (key) {
-                            case MediaMetadataEditor.RATING_KEY_BY_USER:
-                                mCurrentRcClient.updateMetadata(genId, key, value);
-                                break;
-                            default:
-                                Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
-                                        + genId);
-                                break;
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Current valid remote client is dead", e);
-                        mCurrentRcClient = null;
-                    }
-                }
-            }
-        }
-    }
-
-    protected void setPlaybackInfoForRcc(int rccId, int what, int value) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE,
-                rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */);
-    }
-
-    // handler for MSG_RCC_NEW_PLAYBACK_INFO
-    private void onNewPlaybackInfoForRcc(int rccId, int key, int value) {
-        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId +
-                ", what=" + key + ",val=" + value + ")");
-        synchronized(mPRStack) {
-            // iterating from top of stack as playback information changes are more likely
-            //   on entries at the top of the remote control stack
-            try {
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    final PlayerRecord prse = mPRStack.elementAt(index);
-                    if (prse.getRccId() == rccId) {
-                        switch (key) {
-                            case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE:
-                                prse.mPlaybackType = value;
-                                postReevaluateRemote();
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME:
-                                prse.mPlaybackVolume = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolume = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX:
-                                prse.mPlaybackVolumeMax = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolumeMax = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING:
-                                prse.mPlaybackVolumeHandling = value;
-                                synchronized (mMainRemote) {
-                                    if (rccId == mMainRemote.mRccId) {
-                                        mMainRemote.mVolumeHandling = value;
-                                        mVolumeController.postHasNewRemotePlaybackInfo();
-                                    }
-                                }
-                                break;
-                            case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
-                                prse.mPlaybackStream = value;
-                                break;
-                            default:
-                                Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
-                                break;
-                        }
-                        return;
-                    }
-                }//for
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification
-                Log.e(TAG, "Wrong index mPRStack on onNewPlaybackInfoForRcc, lock error? ", e);
-            }
-        }
-    }
-
-    protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
-                rccId /* arg1 */, state /* arg2 */,
-                new PlayerRecord.RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
-    }
-
-    private void onNewPlaybackStateForRcc(int rccId, int state,
-            PlayerRecord.RccPlaybackState newState) {
-        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
-                + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
-        synchronized(mPRStack) {
-            if (mPRStack.empty()) {
-                return;
-            }
-            PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
-            PlayerRecord prse = null;
-            int lastPlayingIndex = mPRStack.size();
-            int inStackIndex = -1;
-            try {
-                // go through the stack from the top to figure out who's playing, and the position
-                // of this RemoteControlClient (note that it may not be in the stack)
-                for (int index = mPRStack.size()-1; index >= 0; index--) {
-                    prse = mPRStack.elementAt(index);
-                    if (prse.getRccId() == rccId) {
-                        inStackIndex = index;
-                        prse.mPlaybackState = newState;
-                    }
-                    if (prse.isPlaybackActive()) {
-                        lastPlayingIndex = index;
-                    }
-                }
-
-                if (inStackIndex != -1) {
-                    // is in the stack
-                    prse = mPRStack.elementAt(inStackIndex);
-                    synchronized (mMainRemote) {
-                        if (rccId == mMainRemote.mRccId) {
-                            mMainRemoteIsActive = isPlaystateActive(state);
-                            postReevaluateRemote();
-                        }
-                    }
-                    if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
-                        // remove it from its old location in the stack
-                        mPRStack.removeElementAt(inStackIndex);
-                        if (prse.isPlaybackActive()) {
-                            // and put it at the top
-                            mPRStack.push(prse);
-                        } else {
-                            // and put it after the ones with active playback
-                            if (inStackIndex > lastPlayingIndex) {
-                                mPRStack.add(lastPlayingIndex, prse);
-                            } else {
-                                mPRStack.add(lastPlayingIndex - 1, prse);
-                            }
-                        }
-                    }
-
-                    if (oldTopPrse != mPRStack.lastElement()) {
-                        // the top of the stack changed:
-                        final ComponentName target =
-                                mPRStack.lastElement().getMediaButtonReceiver();
-                        if (target != null) {
-                            // post message to persist the default media button receiver
-                            mEventHandler.sendMessage( mEventHandler.obtainMessage(
-                                    MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
-                        }
-                        // reevaluate the display
-                        checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                // not expected to happen, indicates improper concurrent modification or bad index
-                Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
-                        + " size=" + mPRStack.size()
-                        + "accessing PlayerRecord stack in onNewPlaybackStateForRcc", e);
-            }
-        }
-    }
-
-    protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
-        sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE,
-                rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */);
-    }
-
     // handler for MSG_RCC_NEW_VOLUME_OBS
     private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
         synchronized(mPRStack) {
@@ -2293,29 +2061,6 @@
         }
     }
 
-    protected void adjustRemoteVolume(int streamType, int direction, int flags) {
-        int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
-        boolean volFixed = false;
-        synchronized (mMainRemote) {
-            if (!mMainRemoteIsActive) {
-                if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client");
-                return;
-            }
-            rccId = mMainRemote.mRccId;
-            volFixed = (mMainRemote.mVolumeHandling ==
-                    RemoteControlClient.PLAYBACK_VOLUME_FIXED);
-        }
-        // unlike "local" stream volumes, we can't compute the new volume based on the direction,
-        // we can only notify the remote that volume needs to be updated, and we'll get an async'
-        // update through setPlaybackInfoForRcc()
-        if (!volFixed) {
-            sendVolumeUpdateToRemote(rccId, direction);
-        }
-
-        // fire up the UI
-        mVolumeController.postRemoteVolumeChanged(streamType, flags);
-    }
-
     private void sendVolumeUpdateToRemote(int rccId, int direction) {
         if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
         if (direction == 0) {
@@ -2415,27 +2160,9 @@
     }
 
     private void onReevaluateRemote() {
-        if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
-        // is there a registered RemoteControlClient that is handling remote playback
-        boolean hasRemotePlayback = false;
-        synchronized (mPRStack) {
-            // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack
-            //   traversal order doesn't matter
-            Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
-            while(stackIterator.hasNext()) {
-                PlayerRecord prse = stackIterator.next();
-                if (prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
-                    hasRemotePlayback = true;
-                    break;
-                }
-            }
-        }
-        synchronized (mMainRemote) {
-            if (mHasRemotePlayback != hasRemotePlayback) {
-                mHasRemotePlayback = hasRemotePlayback;
-                mVolumeController.postRemoteSliderVisibility(hasRemotePlayback);
-            }
-        }
+        // TODO This was used to notify VolumePanel if there was remote playback
+        // in the stack. This is now in MediaSessionService. More code should be
+        // removed.
     }
 
 }
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 5dc8e1b..5d2f3bd 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -260,7 +260,7 @@
             rating = mBundle.getParcelable(key);
         } catch (Exception e) {
             // ignore, value was not a bitmap
-            Log.d(TAG, "Failed to retrieve a key as Rating.", e);
+            Log.w(TAG, "Failed to retrieve a key as Rating.", e);
         }
         return rating;
     }
@@ -278,7 +278,7 @@
             bmp = mBundle.getParcelable(key);
         } catch (Exception e) {
             // ignore, value was not a bitmap
-            Log.d(TAG, "Failed to retrieve a key as Bitmap.", e);
+            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
         }
         return bmp;
     }
@@ -323,8 +323,8 @@
         return EDITOR_KEY_MAPPING.get(editorKey, null);
     }
 
-    public static final Parcelable.Creator<MediaMetadata> CREATOR
-            = new Parcelable.Creator<MediaMetadata>() {
+    public static final Parcelable.Creator<MediaMetadata> CREATOR =
+            new Parcelable.Creator<MediaMetadata>() {
                 @Override
                 public MediaMetadata createFromParcel(Parcel in) {
                     return new MediaMetadata(in);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1b92410..d1909bc 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1649,8 +1649,8 @@
                 mFormat = MediaFormat.createSubtitleFormat(
                     MEDIA_MIMETYPE_TEXT_SUBRIP, language);
             } else if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-                mFormat = MediaFormat.createSubtitleFormat(
-                    MEDIA_MIMETYPE_TEXT_VTT, language);
+                String mime = in.readString();
+                mFormat = MediaFormat.createSubtitleFormat(mime, language);
                 mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt());
                 mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt());
                 mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt());
@@ -1683,12 +1683,40 @@
             dest.writeString(getLanguage());
 
             if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+                dest.writeString(mFormat.getString(MediaFormat.KEY_MIME));
                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT));
                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT));
                 dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE));
             }
         }
 
+        @Override
+        public String toString() {
+            StringBuilder out = new StringBuilder(128);
+            out.append(getClass().getName());
+            out.append('{');
+            switch (mTrackType) {
+            case MEDIA_TRACK_TYPE_VIDEO:
+                out.append("VIDEO");
+                break;
+            case MEDIA_TRACK_TYPE_AUDIO:
+                out.append("AUDIO");
+                break;
+            case MEDIA_TRACK_TYPE_TIMEDTEXT:
+                out.append("TIMEDTEXT");
+                break;
+            case MEDIA_TRACK_TYPE_SUBTITLE:
+                out.append("SUBTITLE");
+                break;
+            default:
+                out.append("UNKNOWN");
+                break;
+            }
+            out.append(", " + mFormat.toString());
+            out.append("}");
+            return out.toString();
+        }
+
         /**
          * Used to read a TrackInfo from a Parcel.
          */
@@ -1757,6 +1785,12 @@
      */
     public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt";
 
+    /**
+     * MIME type for CEA-608 closed caption data.
+     * @hide
+     */
+    public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608";
+
     /*
      * A helper function to check if the mime type is supported by media framework.
      */
@@ -1792,16 +1826,7 @@
             }
             SubtitleTrack track = mInbandSubtitleTracks[index];
             if (track != null) {
-                try {
-                    long runID = data.getStartTimeUs() + 1;
-                    // TODO: move conversion into track
-                    track.onData(new String(data.getData(), "UTF-8"), true /* eos */, runID);
-                    track.setRunDiscardTimeMs(
-                            runID,
-                            (data.getStartTimeUs() + data.getDurationUs()) / 1000);
-                } catch (java.io.UnsupportedEncodingException e) {
-                    Log.w(TAG, "subtitle data for track " + index + " is not UTF-8 encoded: " + e);
-                }
+                track.onData(data);
             }
         }
     };
@@ -1872,7 +1897,7 @@
                 }
                 scanner.close();
                 mOutOfBandSubtitleTracks.add(track);
-                track.onData(contents, true /* eos */, ~0 /* runID: keep forever */);
+                track.onData(contents.getBytes(), true /* eos */, ~0 /* runID: keep forever */);
                 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
             }
 
@@ -2310,7 +2335,9 @@
                 case MEDIA_INFO_EXTERNAL_METADATA_UPDATE:
                     msg.arg1 = MEDIA_INFO_METADATA_UPDATE;
                     // update default track selection
-                    mSubtitleController.selectDefaultTrack();
+                    if (mSubtitleController != null) {
+                        mSubtitleController.selectDefaultTrack();
+                    }
                     break;
                 }
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 1da0215..3336694 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -29,7 +29,6 @@
 import android.hardware.display.WifiDisplay;
 import android.hardware.display.WifiDisplayStatus;
 import android.media.session.MediaSession;
-import android.media.session.RemoteVolumeProvider;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
@@ -60,7 +59,6 @@
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean USE_SESSIONS = true;
 
     static class Static implements DisplayManager.DisplayListener {
         final Context mAppContext;
@@ -2104,11 +2102,7 @@
         public void setPlaybackType(int type) {
             if (mPlaybackType != type) {
                 mPlaybackType = type;
-                if (USE_SESSIONS) {
-                    configureSessionVolume();
-                } else {
-                    setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
-                }
+                configureSessionVolume();
             }
         }
 
@@ -2121,12 +2115,7 @@
         public void setVolumeHandling(int volumeHandling) {
             if (mVolumeHandling != volumeHandling) {
                 mVolumeHandling = volumeHandling;
-                if (USE_SESSIONS) {
-                    configureSessionVolume();
-                } else {
-                    setPlaybackInfoOnRcc(
-                            RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
-                }
+                configureSessionVolume();
             }
         }
 
@@ -2139,12 +2128,8 @@
             volume = Math.max(0, Math.min(volume, getVolumeMax()));
             if (mVolume != volume) {
                 mVolume = volume;
-                if (USE_SESSIONS) {
-                    if (mSvp != null) {
-                        mSvp.notifyVolumeChanged();
-                    }
-                } else {
-                    setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
+                if (mSvp != null) {
+                    mSvp.notifyVolumeChanged();
                 }
                 dispatchRouteVolumeChanged(this);
                 if (mGroup != null) {
@@ -2184,11 +2169,7 @@
         public void setVolumeMax(int volumeMax) {
             if (mVolumeMax != volumeMax) {
                 mVolumeMax = volumeMax;
-                if (USE_SESSIONS) {
-                    configureSessionVolume();
-                } else {
-                    setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
-                }
+                configureSessionVolume();
             }
         }
 
@@ -2199,40 +2180,12 @@
         public void setPlaybackStream(int stream) {
             if (mPlaybackStream != stream) {
                 mPlaybackStream = stream;
-                if (USE_SESSIONS) {
-                    configureSessionVolume();
-                } else {
-                    setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
-                }
+                configureSessionVolume();
             }
         }
 
         private void updatePlaybackInfoOnRcc() {
-            if (USE_SESSIONS) {
-                configureSessionVolume();
-            } else {
-                if ((mRcc != null)
-                        && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
-                    mRcc.setPlaybackInformation(
-                            RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
-                    mRcc.setPlaybackInformation(
-                            RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
-                    mRcc.setPlaybackInformation(
-                            RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
-                    mRcc.setPlaybackInformation(
-                            RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
-                    mRcc.setPlaybackInformation(
-                            RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
-                    // let AudioService know whom to call when remote volume
-                    // needs to be updated
-                    try {
-                        sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
-                                mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error registering remote volume observer", e);
-                    }
-                }
-            }
+            configureSessionVolume();
         }
 
         private void configureSessionVolume() {
@@ -2250,10 +2203,10 @@
                 return;
             }
             if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
-                int volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_FIXED;
+                int volumeControl = VolumeProvider.VOLUME_CONTROL_FIXED;
                 switch (mVolumeHandling) {
                     case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
-                        volumeControl = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+                        volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
                         break;
                     case RemoteControlClient.PLAYBACK_VOLUME_FIXED:
                     default:
@@ -2272,13 +2225,7 @@
             }
         }
 
-        private void setPlaybackInfoOnRcc(int what, int value) {
-            if (mRcc != null) {
-                mRcc.setPlaybackInformation(what, value);
-            }
-        }
-
-        class SessionVolumeProvider extends RemoteVolumeProvider {
+        class SessionVolumeProvider extends VolumeProvider {
 
             public SessionVolumeProvider(int volumeControl, int maxVolume) {
                 super(volumeControl, maxVolume);
diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java
index 23c3652..664308c 100644
--- a/media/java/android/media/MiniThumbFile.java
+++ b/media/java/android/media/MiniThumbFile.java
@@ -248,7 +248,8 @@
                 long magic = mBuffer.getLong();
                 int length = mBuffer.getInt();
 
-                if (size >= 1 + 8 + 4 + length && data.length >= length) {
+                if (size >= 1 + 8 + 4 + length && length != 0 && magic != 0 && flag == 1 &&
+                        data.length >= length) {
                     mBuffer.get(data, 0, length);
                     return data;
                 }
diff --git a/media/java/android/media/PlayerRecord.java b/media/java/android/media/PlayerRecord.java
index f9708c3..664ddcf 100644
--- a/media/java/android/media/PlayerRecord.java
+++ b/media/java/android/media/PlayerRecord.java
@@ -56,7 +56,7 @@
      */
     final private ComponentName mReceiverComponent;
 
-    private int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+    private int mRccId = -1;
 
     /**
      * A non-null token implies this record tracks a "live" player whose death is being monitored.
diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java
index f4fbe2c..a518bb4 100644
--- a/media/java/android/media/Rating.java
+++ b/media/java/android/media/Rating.java
@@ -30,6 +30,7 @@
  */
 public final class Rating implements Parcelable {
     private final static String TAG = "Rating";
+
     /**
      * Indicates a rating style is not supported. A Rating will never have this
      * type, but can be used by other classes to indicate they do not support
@@ -79,12 +80,8 @@
         mRatingValue = rating;
     }
 
-
-    /**
-     * @hide
-     */
     @Override
-    public String toString () {
+    public String toString() {
         return "Rating:style=" + mRatingStyle + " rating="
                 + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
     }
@@ -107,9 +104,12 @@
          * @param p    Parcel object to read the Rating from
          * @return a new Rating created from the data in the parcel
          */
+        @Override
         public Rating createFromParcel(Parcel p) {
             return new Rating(p.readInt(), p.readFloat());
         }
+
+        @Override
         public Rating[] newArray(int size) {
             return new Rating[size];
         }
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 0caea5f..73bc61a 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -561,6 +561,8 @@
                 return;
             }
             synchronized (mCacheLock) {
+                // Still build the old metadata so when creating a new editor
+                // you get the expected values.
                 // assign the edited data
                 mMetadata = new Bundle(mEditorMetadata);
                 // add the information about editable keys
@@ -570,16 +572,6 @@
                 }
                 mOriginalArtwork = mEditorArtwork;
                 mEditorArtwork = null;
-                if (mMetadataChanged & mArtworkChanged) {
-                    // send to remote control display if conditions are met
-                    sendMetadataWithArtwork_syncCacheLock(null, 0, 0);
-                } else if (mMetadataChanged) {
-                    // send to remote control display if conditions are met
-                    sendMetadata_syncCacheLock(null);
-                } else if (mArtworkChanged) {
-                    // send to remote control display if conditions are met
-                    sendArtwork_syncCacheLock(null, 0, 0);
-                }
 
                 // USE_SESSIONS
                 if (mSession != null && mMetadataBuilder != null) {
@@ -687,14 +679,6 @@
                 // keep track of when the state change occurred
                 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
 
-                // send to remote control display if conditions are met
-                sendPlaybackState_syncCacheLock(null);
-                // update AudioService
-                sendAudioServiceNewPlaybackState_syncCacheLock();
-
-                // handle automatic playback position refreshes
-                initiateCheckForDrift_syncCacheLock();
-
                 // USE_SESSIONS
                 if (mSession != null) {
                     int pbState = PlaybackState.getStateFromRccState(state);
@@ -707,29 +691,7 @@
         }
     }
 
-    private void initiateCheckForDrift_syncCacheLock() {
-        if (mEventHandler == null) {
-            return;
-        }
-        mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
-        if (!mNeedsPositionSync) {
-            return;
-        }
-        if (mPlaybackPositionMs < 0) {
-            // the current playback state has no known playback position, it's no use
-            // trying to see if there is any drift at this point
-            // (this also bypasses this mechanism for older apps that use the old
-            //  setPlaybackState(int) API)
-            return;
-        }
-        if (playbackPositionShouldMove(mPlaybackState)) {
-            // playback position moving, schedule next position drift check
-            mEventHandler.sendMessageDelayed(
-                    mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
-                    getCheckPeriodFromSpeed(mPlaybackSpeed));
-        }
-    }
-
+    // TODO investigate if we still need position drift checking
     private void onPositionDriftCheck() {
         if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
         synchronized(mCacheLock) {
@@ -781,9 +743,6 @@
             // store locally
             mTransportControlFlags = transportControlFlags;
 
-            // send to remote control display if conditions are met
-            sendTransportControlInfo_syncCacheLock(null);
-
             // USE_SESSIONS
             if (mSession != null) {
                 mSessionPlaybackState.setActions(PlaybackState
@@ -866,17 +825,7 @@
      */
     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
         synchronized(mCacheLock) {
-            int oldCapa = mPlaybackPositionCapabilities;
-            if (l != null) {
-                mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE;
-            } else {
-                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE;
-            }
             mPositionUpdateListener = l;
-            if (oldCapa != mPlaybackPositionCapabilities) {
-                // tell RCDs that this RCC's playback position capabilities have changed
-                sendTransportControlInfo_syncCacheLock(null);
-            }
         }
     }
 
@@ -888,17 +837,7 @@
      */
     public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
         synchronized(mCacheLock) {
-            int oldCapa = mPlaybackPositionCapabilities;
-            if (l != null) {
-                mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE;
-            } else {
-                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE;
-            }
             mPositionProvider = l;
-            if (oldCapa != mPlaybackPositionCapabilities) {
-                // tell RCDs that this RCC's playback position capabilities have changed
-                sendTransportControlInfo_syncCacheLock(null);
-            }
             if ((mPositionProvider != null) && (mEventHandler != null)
                     && playbackPositionShouldMove(mPlaybackState)) {
                 // playback position is already moving, but now we have a position provider,
@@ -925,124 +864,12 @@
      */
     public static int MEDIA_POSITION_WRITABLE = 1 << 1;
 
-    private int mPlaybackPositionCapabilities = 0;
-
     /** @hide */
     public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
     /** @hide */
     // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
     public final static int DEFAULT_PLAYBACK_VOLUME = 15;
 
-    private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
-    private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
-    private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
-    private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
-    private int mPlaybackStream = AudioManager.STREAM_MUSIC;
-
-    /**
-     * @hide
-     * Set information describing information related to the playback of media so the system
-     * can implement additional behavior to handle non-local playback usecases.
-     * @param what a key to specify the type of information to set. Valid keys are
-     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
-     *        {@link #PLAYBACKINFO_USES_STREAM},
-     *        {@link #PLAYBACKINFO_VOLUME},
-     *        {@link #PLAYBACKINFO_VOLUME_MAX},
-     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
-     * @param value the value for the supplied information to set.
-     */
-    public void setPlaybackInformation(int what, int value) {
-        synchronized(mCacheLock) {
-            switch (what) {
-                case PLAYBACKINFO_PLAYBACK_TYPE:
-                    if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
-                        if (mPlaybackType != value) {
-                            mPlaybackType = value;
-                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
-                        }
-                    } else {
-                        Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
-                    }
-                    break;
-                case PLAYBACKINFO_VOLUME:
-                    if ((value > -1) && (value <= mPlaybackVolumeMax)) {
-                        if (mPlaybackVolume != value) {
-                            mPlaybackVolume = value;
-                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
-                        }
-                    } else {
-                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
-                    }
-                    break;
-                case PLAYBACKINFO_VOLUME_MAX:
-                    if (value > 0) {
-                        if (mPlaybackVolumeMax != value) {
-                            mPlaybackVolumeMax = value;
-                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
-                        }
-                    } else {
-                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
-                    }
-                    break;
-                case PLAYBACKINFO_USES_STREAM:
-                    if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
-                        mPlaybackStream = value;
-                    } else {
-                        Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
-                    }
-                    break;
-                case PLAYBACKINFO_VOLUME_HANDLING:
-                    if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
-                        if (mPlaybackVolumeHandling != value) {
-                            mPlaybackVolumeHandling = value;
-                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
-                        }
-                    } else {
-                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
-                    }
-                    break;
-                default:
-                    // not throwing an exception or returning an error if more keys are to be
-                    // supported in the future
-                    Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * @hide
-     * Return playback information represented as an integer value.
-     * @param what a key to specify the type of information to retrieve. Valid keys are
-     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
-     *        {@link #PLAYBACKINFO_USES_STREAM},
-     *        {@link #PLAYBACKINFO_VOLUME},
-     *        {@link #PLAYBACKINFO_VOLUME_MAX},
-     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
-     * @return the current value for the given information type, or
-     *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
-     *   the value is unknown.
-     */
-    public int getIntPlaybackInformation(int what) {
-        synchronized(mCacheLock) {
-            switch (what) {
-                case PLAYBACKINFO_PLAYBACK_TYPE:
-                    return mPlaybackType;
-                case PLAYBACKINFO_VOLUME:
-                    return mPlaybackVolume;
-                case PLAYBACKINFO_VOLUME_MAX:
-                    return mPlaybackVolumeMax;
-                case PLAYBACKINFO_USES_STREAM:
-                    return mPlaybackStream;
-                case PLAYBACKINFO_VOLUME_HANDLING:
-                    return mPlaybackVolumeHandling;
-                default:
-                    Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
-                    return PLAYBACKINFO_INVALID_VALUE;
-            }
-        }
-    }
-
     /**
      * Lock for all cached data
      */
@@ -1102,13 +929,6 @@
      * The current remote control client generation ID across the system, as known by this object
      */
     private int mCurrentClientGenId = -1;
-    /**
-     * The remote control client generation ID, the last time it was told it was the current RC.
-     * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
-     * client is the "focused" one, and that whenever this client's info is updated, it needs to
-     * send it to the known IRemoteControlDisplay interfaces.
-     */
-    private int mInternalClientGenId = -2;
 
     /**
      * The media button intent description associated with this remote control client
@@ -1134,186 +954,18 @@
     private MediaMetadata mMediaMetadata;
 
     /**
-     * A class to encapsulate all the information about a remote control display.
-     * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
-     */
-    private class DisplayInfoForClient {
-        /** may never be null */
-        private IRemoteControlDisplay mRcDisplay;
-        private int mArtworkExpectedWidth;
-        private int mArtworkExpectedHeight;
-        private boolean mWantsPositionSync = false;
-        private boolean mEnabled = true;
-
-        DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
-            mRcDisplay = rcd;
-            mArtworkExpectedWidth = w;
-            mArtworkExpectedHeight = h;
-        }
-    }
-
-    /**
-     * The list of remote control displays to which this client will send information.
-     * Accessed and modified synchronized on mCacheLock
-     */
-    private ArrayList<DisplayInfoForClient> mRcDisplays = new ArrayList<DisplayInfoForClient>(1);
-
-    /**
      * @hide
      * Accessor to media button intent description (includes target component)
      */
     public PendingIntent getRcMediaIntent() {
         return mRcMediaIntent;
     }
-    /**
-     * @hide
-     * Accessor to IRemoteControlClient
-     */
-    public IRemoteControlClient getIRemoteControlClient() {
-        return mIRCC;
-    }
-
-    /**
-     * The IRemoteControlClient implementation
-     */
-    private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
-
-        //TODO change name to informationRequestForAllDisplays()
-        public void onInformationRequested(int generationId, int infoFlags) {
-            // only post messages, we can't block here
-            if (mEventHandler != null) {
-                // signal new client
-                mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
-                mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN,
-                                /*arg1*/ generationId, /*arg2, ignored*/ 0));
-                // send the information
-                mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
-                mEventHandler.removeMessages(MSG_REQUEST_METADATA);
-                mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
-                mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
-                mEventHandler.removeMessages(MSG_REQUEST_METADATA_ARTWORK);
-                mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, null));
-                mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, null));
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK,
-                        0, 0, null));
-            }
-        }
-
-        public void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h) {
-            // only post messages, we can't block here
-            if (mEventHandler != null) {
-                mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, rcd));
-                mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, rcd));
-                if ((w > 0) && (h > 0)) {
-                    mEventHandler.sendMessage(
-                            mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, w, h, rcd));
-                } else {
-                    mEventHandler.sendMessage(
-                            mEventHandler.obtainMessage(MSG_REQUEST_METADATA, rcd));
-                }
-            }
-        }
-
-        public void setCurrentClientGenerationId(int clientGeneration) {
-            // only post messages, we can't block here
-            if (mEventHandler != null) {
-                mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
-            }
-        }
-
-        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
-            // only post messages, we can't block here
-            if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_PLUG_DISPLAY, w, h, rcd));
-            }
-        }
-
-        public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
-            // only post messages, we can't block here
-            if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_UNPLUG_DISPLAY, rcd));
-            }
-        }
-
-        public void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h) {
-            // only post messages, we can't block here
-            if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd));
-            }
-        }
-
-        public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) {
-            // only post messages, we can't block here
-            if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd));
-            }
-        }
-
-        public void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled) {
-            // only post messages, we can't block here
-            if ((mEventHandler != null) && (rcd != null)) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_DISPLAY_ENABLE, enabled ? 1 : 0, 0/*arg2 ignored*/, rcd));
-            }
-        }
-
-        public void seekTo(int generationId, long timeMs) {
-            // only post messages, we can't block here
-            if (mEventHandler != null) {
-                mEventHandler.removeMessages(MSG_SEEK_TO);
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */,
-                        new Long(timeMs)));
-            }
-        }
-
-        public void updateMetadata(int generationId, int key, Rating value) {
-            // only post messages, we can't block here
-            if (mEventHandler != null) {
-                mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                        MSG_UPDATE_METADATA, generationId /* arg1 */, key /* arg2*/, value));
-            }
-        }
-    };
 
     /**
      * @hide
      * Default value for the unique identifier
      */
     public final static int RCSE_ID_UNREGISTERED = -1;
-    /**
-     * Unique identifier of the RemoteControlStackEntry in AudioService with which
-     * this RemoteControlClient is associated.
-     */
-    private int mRcseId = RCSE_ID_UNREGISTERED;
-    /**
-     * @hide
-     * To be only used by AudioManager after it has received the unique id from
-     * IAudioService.registerRemoteControlClient()
-     * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
-     *              this RemoteControlClient is associated.
-     */
-    public void setRcseId(int id) {
-        mRcseId = id;
-    }
-
-    /**
-     * @hide
-     */
-    public int getRcseId() {
-        return mRcseId;
-    }
 
     // USE_SESSIONS
     private MediaSession.TransportControlsCallback mTransportListener
@@ -1327,31 +979,13 @@
         @Override
         public void onSetRating(Rating rating) {
             if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) {
-                if (mEventHandler != null) {
-                    mEventHandler.sendMessage(mEventHandler.obtainMessage(
-                            MSG_UPDATE_METADATA, mCurrentClientGenId,
-                            MetadataEditor.RATING_KEY_BY_USER, rating));
-                }
+                onUpdateMetadata(mCurrentClientGenId, MetadataEditor.RATING_KEY_BY_USER, rating);
             }
         }
     };
 
     private EventHandler mEventHandler;
-    private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
-    private final static int MSG_REQUEST_METADATA = 2;
-    private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
-    private final static int MSG_REQUEST_ARTWORK = 4;
-    private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
-    private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
-    private final static int MSG_PLUG_DISPLAY = 7;
-    private final static int MSG_UNPLUG_DISPLAY = 8;
-    private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9;
-    private final static int MSG_SEEK_TO = 10;
     private final static int MSG_POSITION_DRIFT_CHECK = 11;
-    private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12;
-    private final static int MSG_UPDATE_METADATA = 13;
-    private final static int MSG_REQUEST_METADATA_ARTWORK = 14;
-    private final static int MSG_DISPLAY_ENABLE = 15;
 
     private class EventHandler extends Handler {
         public EventHandler(RemoteControlClient rcc, Looper looper) {
@@ -1361,63 +995,9 @@
         @Override
         public void handleMessage(Message msg) {
             switch(msg.what) {
-                case MSG_REQUEST_PLAYBACK_STATE:
-                    synchronized (mCacheLock) {
-                        sendPlaybackState_syncCacheLock((IRemoteControlDisplay)msg.obj);
-                    }
-                    break;
-                case MSG_REQUEST_METADATA:
-                    synchronized (mCacheLock) {
-                        sendMetadata_syncCacheLock((IRemoteControlDisplay)msg.obj);
-                    }
-                    break;
-                case MSG_REQUEST_TRANSPORTCONTROL:
-                    synchronized (mCacheLock) {
-                        sendTransportControlInfo_syncCacheLock((IRemoteControlDisplay)msg.obj);
-                    }
-                    break;
-                case MSG_REQUEST_ARTWORK:
-                    synchronized (mCacheLock) {
-                        sendArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj,
-                                msg.arg1, msg.arg2);
-                    }
-                    break;
-                case MSG_REQUEST_METADATA_ARTWORK:
-                    synchronized (mCacheLock) {
-                        sendMetadataWithArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj,
-                                msg.arg1, msg.arg2);
-                    }
-                    break;
-                case MSG_NEW_INTERNAL_CLIENT_GEN:
-                    onNewInternalClientGen(msg.arg1);
-                    break;
-                case MSG_NEW_CURRENT_CLIENT_GEN:
-                    onNewCurrentClientGen(msg.arg1);
-                    break;
-                case MSG_PLUG_DISPLAY:
-                    onPlugDisplay((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
-                    break;
-                case MSG_UNPLUG_DISPLAY:
-                    onUnplugDisplay((IRemoteControlDisplay)msg.obj);
-                    break;
-                case MSG_UPDATE_DISPLAY_ARTWORK_SIZE:
-                    onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2);
-                    break;
-                case MSG_SEEK_TO:
-                    onSeekTo(msg.arg1, ((Long)msg.obj).longValue());
-                    break;
                 case MSG_POSITION_DRIFT_CHECK:
                     onPositionDriftCheck();
                     break;
-                case MSG_DISPLAY_WANTS_POS_SYNC:
-                    onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
-                    break;
-                case MSG_UPDATE_METADATA:
-                    onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
-                    break;
-                case MSG_DISPLAY_ENABLE:
-                    onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
-                    break;
                 default:
                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
             }
@@ -1425,346 +1005,8 @@
     }
 
     //===========================================================
-    // Communication with the IRemoteControlDisplay (the displays known to the system)
-
-    private void sendPlaybackState_syncCacheLock(IRemoteControlDisplay target) {
-        if (mCurrentClientGenId == mInternalClientGenId) {
-            if (target != null) {
-                try {
-                    target.setPlaybackState(mInternalClientGenId,
-                            mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
-                            mPlaybackSpeed);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setPlaybackState() for dead display " + target, e);
-                }
-                return;
-            }
-            // target == null implies all displays must be updated
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mEnabled) {
-                    try {
-                        di.mRcDisplay.setPlaybackState(mInternalClientGenId,
-                                mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
-                                mPlaybackSpeed);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
-                        displayIterator.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) {
-        if (mCurrentClientGenId == mInternalClientGenId) {
-            if (target != null) {
-                try {
-                    target.setMetadata(mInternalClientGenId, mMetadata);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setMetadata() for dead display " + target, e);
-                }
-                return;
-            }
-            // target == null implies all displays must be updated
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mEnabled) {
-                    try {
-                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
-                        displayIterator.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) {
-        if (mCurrentClientGenId == mInternalClientGenId) {
-            if (target != null) {
-                try {
-                    target.setTransportControlInfo(mInternalClientGenId,
-                            mTransportControlFlags, mPlaybackPositionCapabilities);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setTransportControlFlags() for dead display " + target,
-                            e);
-                }
-                return;
-            }
-            // target == null implies all displays must be updated
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mEnabled) {
-                    try {
-                        di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
-                                mTransportControlFlags, mPlaybackPositionCapabilities);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
-                                e);
-                        displayIterator.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
-        // FIXME modify to cache all requested sizes?
-        if (mCurrentClientGenId == mInternalClientGenId) {
-            if (target != null) {
-                final DisplayInfoForClient di = new DisplayInfoForClient(target, w, h);
-                sendArtworkToDisplay(di);
-                return;
-            }
-            // target == null implies all displays must be updated
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                if (!sendArtworkToDisplay(displayIterator.next())) {
-                    displayIterator.remove();
-                }
-            }
-        }
-    }
-
-    /**
-     * Send artwork to an IRemoteControlDisplay.
-     * @param di encapsulates the IRemoteControlDisplay that will receive the artwork, and its
-     *    dimension requirements.
-     * @return false if there was an error communicating with the IRemoteControlDisplay.
-     */
-    private boolean sendArtworkToDisplay(DisplayInfoForClient di) {
-        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
-            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
-                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
-            try {
-                di.mRcDisplay.setArtwork(mInternalClientGenId, artwork);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error in sendArtworkToDisplay(), dead display " + di.mRcDisplay, e);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void sendMetadataWithArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
-        // FIXME modify to cache all requested sizes?
-        if (mCurrentClientGenId == mInternalClientGenId) {
-            if (target != null) {
-                try {
-                    if ((w > 0) && (h > 0)) {
-                        Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, w, h);
-                        target.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
-                    } else {
-                        target.setMetadata(mInternalClientGenId, mMetadata);
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in set(All)Metadata() for dead display " + target, e);
-                }
-                return;
-            }
-            // target == null implies all displays must be updated
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                try {
-                    if (di.mEnabled) {
-                        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
-                            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
-                                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
-                            di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
-                        } else {
-                            di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
-                    displayIterator.remove();
-                }
-            }
-        }
-    }
-
-    //===========================================================
-    // Communication with AudioService
-
-    private static IAudioService sService;
-
-    private static IAudioService getService()
-    {
-        if (sService != null) {
-            return sService;
-        }
-        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
-        sService = IAudioService.Stub.asInterface(b);
-        return sService;
-    }
-
-    private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
-        if (mRcseId == RCSE_ID_UNREGISTERED) {
-            return;
-        }
-        //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
-        IAudioService service = getService();
-        try {
-            service.setPlaybackInfoForRcc(mRcseId, what, value);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e);
-        }
-    }
-
-    private void sendAudioServiceNewPlaybackState_syncCacheLock() {
-        if (mRcseId == RCSE_ID_UNREGISTERED) {
-            return;
-        }
-        IAudioService service = getService();
-        try {
-            service.setPlaybackStateForRcc(mRcseId,
-                    mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in setPlaybackStateForRcc", e);
-        }
-    }
-
-    //===========================================================
     // Message handlers
 
-    private void onNewInternalClientGen(int clientGeneration) {
-        synchronized (mCacheLock) {
-            // this remote control client is told it is the "focused" one:
-            // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
-            mInternalClientGenId = clientGeneration;
-        }
-    }
-
-    private void onNewCurrentClientGen(int clientGeneration) {
-        synchronized (mCacheLock) {
-            mCurrentClientGenId = clientGeneration;
-        }
-    }
-
-    /** pre-condition rcd != null */
-    private void onPlugDisplay(IRemoteControlDisplay rcd, int w, int h) {
-        synchronized(mCacheLock) {
-            // do we have this display already?
-            boolean displayKnown = false;
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext() && !displayKnown) {
-                final DisplayInfoForClient di = displayIterator.next();
-                displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
-                if (displayKnown) {
-                    // this display was known but the change in artwork size will cause the
-                    // artwork to be refreshed
-                    if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
-                        di.mArtworkExpectedWidth = w;
-                        di.mArtworkExpectedHeight = h;
-                        if (!sendArtworkToDisplay(di)) {
-                            displayIterator.remove();
-                        }
-                    }
-                }
-            }
-            if (!displayKnown) {
-                mRcDisplays.add(new DisplayInfoForClient(rcd, w, h));
-            }
-        }
-    }
-
-    /** pre-condition rcd != null */
-    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
-        synchronized(mCacheLock) {
-            Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    displayIterator.remove();
-                    break;
-                }
-            }
-            // list of RCDs has changed, reevaluate whether position check is still needed
-            boolean oldNeedsPositionSync = mNeedsPositionSync;
-            boolean newNeedsPositionSync = false;
-            displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mWantsPositionSync) {
-                    newNeedsPositionSync = true;
-                    break;
-                }
-            }
-            mNeedsPositionSync = newNeedsPositionSync;
-            if (oldNeedsPositionSync != mNeedsPositionSync) {
-                // update needed?
-                initiateCheckForDrift_syncCacheLock();
-            }
-        }
-    }
-
-    /** pre-condition rcd != null */
-    private void onUpdateDisplayArtworkSize(IRemoteControlDisplay rcd, int w, int h) {
-        synchronized(mCacheLock) {
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
-                        ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
-                    di.mArtworkExpectedWidth = w;
-                    di.mArtworkExpectedHeight = h;
-                    if (di.mEnabled) {
-                        if (!sendArtworkToDisplay(di)) {
-                            displayIterator.remove();
-                        }
-                    }
-                    break;
-                }
-            }
-        }
-    }
-
-    /** pre-condition rcd != null */
-    private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) {
-        synchronized(mCacheLock) {
-            boolean oldNeedsPositionSync = mNeedsPositionSync;
-            boolean newNeedsPositionSync = false;
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            // go through the list of RCDs and for each entry, check both whether this is the RCD
-            //  that gets upated, and whether the list has one entry that wants position sync
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mEnabled) {
-                    if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                        di.mWantsPositionSync = wantsSync;
-                    }
-                    if (di.mWantsPositionSync) {
-                        newNeedsPositionSync = true;
-                    }
-                }
-            }
-            mNeedsPositionSync = newNeedsPositionSync;
-            if (oldNeedsPositionSync != mNeedsPositionSync) {
-                // update needed?
-                initiateCheckForDrift_syncCacheLock();
-            }
-        }
-    }
-
-    /** pre-condition rcd != null */
-    private void onDisplayEnable(IRemoteControlDisplay rcd, boolean enable) {
-        synchronized(mCacheLock) {
-            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
-            while (displayIterator.hasNext()) {
-                final DisplayInfoForClient di = displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    di.mEnabled = enable;
-                }
-            }
-        }
-    }
-
     private void onSeekTo(int generationId, long timeMs) {
         synchronized (mCacheLock) {
             if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
@@ -1785,42 +1027,6 @@
     // Internal utilities
 
     /**
-     * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
-     * If the bitmap fits, then do nothing and return the original.
-     *
-     * @param bitmap
-     * @param maxWidth
-     * @param maxHeight
-     * @return
-     */
-
-    private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
-        if (bitmap != null) {
-            final int width = bitmap.getWidth();
-            final int height = bitmap.getHeight();
-            if (width > maxWidth || height > maxHeight) {
-                float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
-                int newWidth = Math.round(scale * width);
-                int newHeight = Math.round(scale * height);
-                Bitmap.Config newConfig = bitmap.getConfig();
-                if (newConfig == null) {
-                    newConfig = Bitmap.Config.ARGB_8888;
-                }
-                Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
-                Canvas canvas = new Canvas(outBitmap);
-                Paint paint = new Paint();
-                paint.setAntiAlias(true);
-                paint.setFilterBitmap(true);
-                canvas.drawBitmap(bitmap, null,
-                        new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
-                bitmap = outBitmap;
-            }
-        }
-        return bitmap;
-    }
-
-
-    /**
      * Returns whether, for the given playback state, the playback position is expected to
      * be changing.
      * @param playstate the playback state to evaluate
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 76c7299..1f5b216 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -35,6 +35,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -361,18 +362,10 @@
         if (timeMs < 0) {
             throw new IllegalArgumentException("illegal negative time value");
         }
-        if (USE_SESSIONS) {
-            synchronized (mInfoLock) {
-                if (mCurrentSession != null) {
-                    mCurrentSession.getTransportControls().seekTo(timeMs);
-                }
+        synchronized (mInfoLock) {
+            if (mCurrentSession != null) {
+                mCurrentSession.getTransportControls().seekTo(timeMs);
             }
-        } else {
-            final int genId;
-            synchronized (mGenLock) {
-                genId = mClientGenerationIdCurrent;
-            }
-            mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
         }
         return true;
     }
@@ -534,34 +527,15 @@
             if (!mMetadataChanged) {
                 return;
             }
-            if (USE_SESSIONS) {
-                synchronized (mInfoLock) {
-                    if (mCurrentSession != null) {
-                        if (mEditorMetadata.containsKey(
-                                String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
-                            Rating rating = (Rating) getObject(
-                                    MediaMetadataEditor.RATING_KEY_BY_USER, null);
-                            if (rating != null) {
-                                mCurrentSession.getTransportControls().setRating(rating);
-                            }
-                        }
-                    }
-                }
-            } else {
-                final int genId;
-                synchronized(mGenLock) {
-                    genId = mClientGenerationIdCurrent;
-                }
-                synchronized(mInfoLock) {
+            synchronized (mInfoLock) {
+                if (mCurrentSession != null) {
                     if (mEditorMetadata.containsKey(
                             String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
                         Rating rating = (Rating) getObject(
                                 MediaMetadataEditor.RATING_KEY_BY_USER, null);
-                        mAudioManager.updateRemoteControlClientMetadata(genId,
-                              MediaMetadataEditor.RATING_KEY_BY_USER,
-                              rating);
-                    } else {
-                        Log.e(TAG, "no metadata to apply");
+                        if (rating != null) {
+                            mCurrentSession.getTransportControls().setRating(rating);
+                        }
                     }
                 }
             }
@@ -820,12 +794,12 @@
         final ComponentName listenerComponent = new ComponentName(mContext,
                 mOnClientUpdateListener.getClass());
         mSessionManager.addActiveSessionsListener(mSessionListener, listenerComponent,
-                ActivityManager.getCurrentUser());
+                UserHandle.myUserId());
         mSessionListener.onActiveSessionsChanged(mSessionManager
                 .getActiveSessions(listenerComponent));
         if (DEBUG) {
             Log.d(TAG, "Registered session listener with component " + listenerComponent
-                    + " for user " + ActivityManager.getCurrentUser());
+                    + " for user " + UserHandle.myUserId());
         }
     }
 
@@ -836,7 +810,7 @@
         mSessionManager.removeActiveSessionsListener(mSessionListener);
         if (DEBUG) {
             Log.d(TAG, "Unregistered session listener for user "
-                    + ActivityManager.getCurrentUser());
+                    + UserHandle.myUserId());
         }
     }
 
diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java
index 06063de..9fedf63 100644
--- a/media/java/android/media/SubtitleTrack.java
+++ b/media/java/android/media/SubtitleTrack.java
@@ -75,6 +75,14 @@
 
     private long mNextScheduledTimeMs = -1;
 
+    protected void onData(SubtitleData data) {
+        long runID = data.getStartTimeUs() + 1;
+        onData(data.getData(), true /* eos */, runID);
+        setRunDiscardTimeMs(
+                runID,
+                (data.getStartTimeUs() + data.getDurationUs()) / 1000);
+    }
+
     /**
      * Called when there is input data for the subtitle track.  The
      * complete subtitle for a track can include multiple whole units
@@ -83,7 +91,7 @@
      * indicating the last section of the run.  Calls from different
      * runs must not be intermixed.
      *
-     * @param data
+     * @param data subtitle data byte buffer
      * @param eos true if this is the last section of the run.
      * @param runID mostly-unique ID for this run of data.  Subtitle cues
      *              with runID of 0 are discarded immediately after
@@ -92,10 +100,8 @@
      *              with other runID-s are discarded at the end of the
      *              run, which defaults to the latest timestamp of
      *              any of its cues (with this runID).
-     *
-     * TODO use ByteBuffer
      */
-    public abstract void onData(String data, boolean eos, long runID);
+    public abstract void onData(byte[] data, boolean eos, long runID);
 
     /**
      * Called when adding the subtitle rendering widget to the view hierarchy,
diff --git a/media/java/android/media/TtmlRenderer.java b/media/java/android/media/TtmlRenderer.java
index 0309334..75133c9 100644
--- a/media/java/android/media/TtmlRenderer.java
+++ b/media/java/android/media/TtmlRenderer.java
@@ -563,28 +563,35 @@
     }
 
     @Override
-    public void onData(String data, boolean eos, long runID) {
-        // implement intermixing restriction for TTML.
-        synchronized(mParser) {
-            if (mCurrentRunID != null && runID != mCurrentRunID) {
-                throw new IllegalStateException(
-                        "Run #" + mCurrentRunID +
-                        " in progress.  Cannot process run #" + runID);
-            }
-            mCurrentRunID = runID;
-            mParsingData += data;
-            if (eos) {
-                try {
-                    mParser.parse(mParsingData, mCurrentRunID);
-                } catch (XmlPullParserException e) {
-                    e.printStackTrace();
-                } catch (IOException e) {
-                    e.printStackTrace();
+    public void onData(byte[] data, boolean eos, long runID) {
+        try {
+            // TODO: handle UTF-8 conversion properly
+            String str = new String(data, "UTF-8");
+
+            // implement intermixing restriction for TTML.
+            synchronized(mParser) {
+                if (mCurrentRunID != null && runID != mCurrentRunID) {
+                    throw new IllegalStateException(
+                            "Run #" + mCurrentRunID +
+                            " in progress.  Cannot process run #" + runID);
                 }
-                finishedRun(runID);
-                mParsingData = "";
-                mCurrentRunID = null;
+                mCurrentRunID = runID;
+                mParsingData += str;
+                if (eos) {
+                    try {
+                        mParser.parse(mParsingData, mCurrentRunID);
+                    } catch (XmlPullParserException e) {
+                        e.printStackTrace();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                    finishedRun(runID);
+                    mParsingData = "";
+                    mCurrentRunID = null;
+                }
             }
+        } catch (java.io.UnsupportedEncodingException e) {
+            Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
         }
     }
 
diff --git a/media/java/android/media/VolumeController.java b/media/java/android/media/VolumeController.java
deleted file mode 100644
index 6b70cc3..0000000
--- a/media/java/android/media/VolumeController.java
+++ /dev/null
@@ -1,135 +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 android.media;
-
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.Objects;
-
-/**
- * Wraps the remote volume controller interface as a convenience to audio service.
- * @hide
- */
-public class VolumeController {
-    private static final String TAG = "VolumeController";
-
-    private IVolumeController mController;
-
-    public void setController(IVolumeController controller) {
-        mController = controller;
-    }
-
-    public boolean isSameBinder(IVolumeController controller) {
-        return Objects.equals(asBinder(), binder(controller));
-    }
-
-    public IBinder asBinder() {
-        return binder(mController);
-    }
-
-    private static IBinder binder(IVolumeController controller) {
-        return controller == null ? null : controller.asBinder();
-    }
-
-    @Override
-    public String toString() {
-        return "VolumeController(" + asBinder() + ")";
-    }
-
-    public void postHasNewRemotePlaybackInfo() {
-        if (mController == null) return;
-        try {
-            mController.hasNewRemotePlaybackInfo();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling hasNewRemotePlaybackInfo", e);
-        }
-    }
-
-    public void postRemoteVolumeChanged(int streamType, int flags) {
-        if (mController == null) return;
-        try {
-            mController.remoteVolumeChanged(streamType, flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling remoteVolumeChanged", e);
-        }
-    }
-
-    public void postRemoteSliderVisibility(boolean visible) {
-        if (mController == null) return;
-        try {
-            mController.remoteSliderVisibility(visible);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling remoteSliderVisibility", e);
-        }
-    }
-
-    public void postDisplaySafeVolumeWarning(int flags) {
-        if (mController == null) return;
-        try {
-            mController.displaySafeVolumeWarning(flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling displaySafeVolumeWarning", e);
-        }
-    }
-
-    public void postVolumeChanged(int streamType, int flags) {
-        if (mController == null) return;
-        try {
-            mController.volumeChanged(streamType, flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling volumeChanged", e);
-        }
-    }
-
-    public void postMasterVolumeChanged(int flags) {
-        if (mController == null) return;
-        try {
-            mController.masterVolumeChanged(flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling masterVolumeChanged", e);
-        }
-    }
-
-    public void postMasterMuteChanged(int flags) {
-        if (mController == null) return;
-        try {
-            mController.masterMuteChanged(flags);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling masterMuteChanged", e);
-        }
-    }
-
-    public void setLayoutDirection(int layoutDirection) {
-        if (mController == null) return;
-        try {
-            mController.setLayoutDirection(layoutDirection);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling setLayoutDirection", e);
-        }
-    }
-
-    public void postDismiss() {
-        if (mController == null) return;
-        try {
-            mController.dismiss();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling dismiss", e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
new file mode 100644
index 0000000..d151e66
--- /dev/null
+++ b/media/java/android/media/VolumeProvider.java
@@ -0,0 +1,130 @@
+/*
+ * 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.media;
+
+import android.media.session.MediaSession;
+
+/**
+ * Handles requests to adjust or set the volume on a session. This is also used
+ * to push volume updates back to the session after a request has been handled.
+ * You can set a volume provider on a session by calling
+ * {@link MediaSession#setPlaybackToRemote}.
+ */
+public abstract class VolumeProvider {
+    /**
+     * The volume is fixed and can not be modified. Requests to change volume
+     * should be ignored.
+     */
+    public static final int VOLUME_CONTROL_FIXED = 0;
+
+    /**
+     * The volume control uses relative adjustment via
+     * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
+     * value should be ignored.
+     */
+    public static final int VOLUME_CONTROL_RELATIVE = 1;
+
+    /**
+     * The volume control uses an absolute value. It may be adjusted using
+     * {@link #onAdjustVolumeBy(int)} or set directly using
+     * {@link #onSetVolumeTo(int)}.
+     */
+    public static final int VOLUME_CONTROL_ABSOLUTE = 2;
+
+    private final int mControlType;
+    private final int mMaxVolume;
+    private Callback mCallback;
+
+    /**
+     * Create a new volume provider for handling volume events. You must specify
+     * the type of volume control and the maximum volume that can be used.
+     *
+     * @param volumeControl The method for controlling volume that is used by
+     *            this provider.
+     * @param maxVolume The maximum allowed volume.
+     */
+    public VolumeProvider(int volumeControl, int maxVolume) {
+        mControlType = volumeControl;
+        mMaxVolume = maxVolume;
+    }
+
+    /**
+     * Get the current volume of the remote playback.
+     *
+     * @return The current volume.
+     */
+    public abstract int onGetCurrentVolume();
+
+    /**
+     * Get the volume control type that this volume provider uses.
+     *
+     * @return The volume control type for this volume provider
+     */
+    public final int getVolumeControl() {
+        return mControlType;
+    }
+
+    /**
+     * Get the maximum volume this provider allows.
+     *
+     * @return The max allowed volume.
+     */
+    public final int getMaxVolume() {
+        return mMaxVolume;
+    }
+
+    /**
+     * Notify the system that the volume has been changed.
+     */
+    public final void notifyVolumeChanged() {
+        if (mCallback != null) {
+            mCallback.onVolumeChanged(this);
+        }
+    }
+
+    /**
+     * Override to handle requests to set the volume of the current output.
+     *
+     * @param volume The volume to set the output to.
+     */
+    public void onSetVolumeTo(int volume) {
+    }
+
+    /**
+     * Override to handle requests to adjust the volume of the current
+     * output.
+     *
+     * @param delta The amount to change the volume
+     */
+    public void onAdjustVolumeBy(int delta) {
+    }
+
+    /**
+     * Sets a callback to receive volume changes.
+     * @hide
+     */
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Listens for changes to the volume.
+     * @hide
+     */
+    public static abstract class Callback {
+        public abstract void onVolumeChanged(VolumeProvider volumeProvider);
+    }
+}
\ No newline at end of file
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 7977988..a9374d5 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -1001,22 +1001,28 @@
     }
 
     @Override
-    public void onData(String data, boolean eos, long runID) {
-        // implement intermixing restriction for WebVTT only for now
-        synchronized(mParser) {
-            if (mCurrentRunID != null && runID != mCurrentRunID) {
-                throw new IllegalStateException(
-                        "Run #" + mCurrentRunID +
-                        " in progress.  Cannot process run #" + runID);
+    public void onData(byte[] data, boolean eos, long runID) {
+        try {
+            String str = new String(data, "UTF-8");
+
+            // implement intermixing restriction for WebVTT only for now
+            synchronized(mParser) {
+                if (mCurrentRunID != null && runID != mCurrentRunID) {
+                    throw new IllegalStateException(
+                            "Run #" + mCurrentRunID +
+                            " in progress.  Cannot process run #" + runID);
+                }
+                mCurrentRunID = runID;
+                mParser.parse(str);
+                if (eos) {
+                    finishedRun(runID);
+                    mParser.eos();
+                    mRegions.clear();
+                    mCurrentRunID = null;
+                }
             }
-            mCurrentRunID = runID;
-            mParser.parse(data);
-            if (eos) {
-                finishedRun(runID);
-                mParser.eos();
-                mRegions.clear();
-                mCurrentRunID = null;
-            }
+        } catch (java.io.UnsupportedEncodingException e) {
+            Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
         }
     }
 
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 1cfc5bc..5bc0de4 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -15,6 +15,7 @@
 
 package android.media.session;
 
+import android.content.ComponentName;
 import android.media.MediaMetadata;
 import android.media.session.ISessionController;
 import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@
     ISessionController getController();
     void setFlags(int flags);
     void setActive(boolean active);
+    void setMediaButtonReceiver(in ComponentName mbr);
     void destroy();
 
     // These commands are for setting up and communicating with routes
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index f0cd785..b4c11f6 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -20,6 +20,7 @@
 import android.media.Rating;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaSessionInfo;
+import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -38,6 +39,9 @@
     void showRoutePicker();
     MediaSessionInfo getSessionInfo();
     long getFlags();
+    ParcelableVolumeInfo getVolumeAttributes();
+    void adjustVolumeBy(int delta, int flags);
+    void setVolumeTo(int value, int flags);
 
     // These commands are for the TransportController
     void play();
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index e823153..baa1379 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -17,6 +17,7 @@
 
 import android.media.MediaMetadata;
 import android.media.session.RouteInfo;
+import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 
@@ -30,4 +31,5 @@
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
     void onMetadataChanged(in MediaMetadata metadata);
+    void onVolumeInfoChanged(in ParcelableVolumeInfo info);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bd1fa85..dce84d4 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -16,6 +16,7 @@
 package android.media.session;
 
 import android.content.ComponentName;
+import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -34,4 +35,7 @@
     void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
             int userId);
     void removeSessionsListener(in IActiveSessionsListener listener);
+
+    // This is for the system volume UI only
+    void setRemoteVolumeController(in IRemoteVolumeController rvc);
 }
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 5ca7daa..7653e5a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -16,8 +16,12 @@
 
 package android.media.session;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -43,12 +47,13 @@
  * MediaController objects are thread-safe.
  */
 public final class MediaController {
-    private static final String TAG = "SessionController";
+    private static final String TAG = "MediaController";
 
     private static final int MSG_EVENT = 1;
     private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
     private static final int MSG_UPDATE_METADATA = 3;
     private static final int MSG_ROUTE = 4;
+    private static final int MSG_UPDATE_VOLUME = 5;
 
     private final ISessionController mSessionBinder;
 
@@ -70,25 +75,18 @@
      * @hide
      */
     public static MediaController fromBinder(ISessionController sessionBinder) {
-        MediaController controller = new MediaController(sessionBinder);
-        try {
-            controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "MediaController created with expired token", e);
-            controller = null;
-        }
-        return controller;
+        return new MediaController(sessionBinder);
     }
 
     /**
-     * Get a new MediaController for a MediaSessionToken. If successful the
-     * controller returned will be connected to the session that generated the
-     * token.
+     * Get a new media controller from a session token which may have
+     * been obtained from another process.  If successful the controller returned
+     * will be connected to the session that generated the token.
      *
-     * @param token The session token to use
-     * @return A controller for the session or null
+     * @param token The session token to control.
+     * @return A controller for the session or null if inaccessible.
      */
-    public static MediaController fromToken(MediaSessionToken token) {
+    public static MediaController fromToken(@NonNull MediaSessionToken token) {
         return fromBinder(token.getBinder());
     }
 
@@ -97,7 +95,7 @@
      *
      * @return A controls instance
      */
-    public TransportControls getTransportControls() {
+    public @NonNull TransportControls getTransportControls() {
         return mTransportController;
     }
 
@@ -108,7 +106,7 @@
      * @param keyEvent The media button event to dispatch.
      * @return true if the event was sent to the session, false otherwise.
      */
-    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
+    public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
         if (keyEvent == null) {
             throw new IllegalArgumentException("KeyEvent may not be null");
         }
@@ -128,7 +126,7 @@
      *
      * @return The current PlaybackState or null
      */
-    public PlaybackState getPlaybackState() {
+    public @Nullable PlaybackState getPlaybackState() {
         try {
             return mSessionBinder.getPlaybackState();
         } catch (RemoteException e) {
@@ -142,7 +140,7 @@
      *
      * @return The current MediaMetadata or null.
      */
-    public MediaMetadata getMetadata() {
+    public @Nullable MediaMetadata getMetadata() {
         try {
             return mSessionBinder.getMetadata();
         } catch (RemoteException e) {
@@ -190,29 +188,86 @@
     }
 
     /**
+     * Get the current volume info for this session.
+     *
+     * @return The current volume info or null.
+     */
+    public @Nullable VolumeInfo getVolumeInfo() {
+        try {
+            ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
+            return new VolumeInfo(result.volumeType, result.audioStream, result.controlType,
+                    result.maxVolume, result.currentVolume);
+
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error calling getVolumeInfo.", e);
+        }
+        return null;
+    }
+
+    /**
+     * Set the volume of the stream or output this session is playing on. The
+     * command will be ignored if it does not support
+     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getVolumeInfo()
+     * @param value The value to set it to, between 0 and the reported max.
+     * @param flags Any flags to pass with the command.
+     */
+    public void setVolumeTo(int value, int flags) {
+        try {
+            mSessionBinder.setVolumeTo(value, flags);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error calling setVolumeTo.", e);
+        }
+    }
+
+    /**
+     * Adjust the volume of the stream or output this session is playing on.
+     * Negative values will lower the volume. The command will be ignored if it
+     * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+     * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getVolumeInfo()
+     * @param delta The number of steps to adjust the volume by.
+     * @param flags Any flags to pass with the command.
+     */
+    public void adjustVolumeBy(int delta, int flags) {
+        try {
+            mSessionBinder.adjustVolumeBy(delta, flags);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
+        }
+    }
+
+    /**
      * Adds a callback to receive updates from the Session. Updates will be
      * posted on the caller's thread.
      *
-     * @param cb The callback object, must not be null
+     * @param callback The callback object, must not be null.
      */
-    public void addCallback(Callback cb) {
-        addCallback(cb, null);
+    public void addCallback(@NonNull Callback callback) {
+        addCallback(callback, null);
     }
 
     /**
      * Adds a callback to receive updates from the session. Updates will be
      * posted on the specified handler's thread.
      *
-     * @param cb Cannot be null.
+     * @param callback The callback object, must not be null.
      * @param handler The handler to post updates on. If null the callers thread
-     *            will be used
+     *            will be used.
      */
-    public void addCallback(Callback cb, Handler handler) {
+    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
         if (handler == null) {
             handler = new Handler();
         }
         synchronized (mLock) {
-            addCallbackLocked(cb, handler);
+            addCallbackLocked(callback, handler);
         }
     }
 
@@ -220,11 +275,14 @@
      * Stop receiving updates on the specified callback. If an update has
      * already been posted you may still receive it after calling this method.
      *
-     * @param cb The callback to remove
+     * @param callback The callback to remove.
      */
-    public void removeCallback(Callback cb) {
+    public void removeCallback(@NonNull Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
         synchronized (mLock) {
-            removeCallbackLocked(cb);
+            removeCallbackLocked(callback);
         }
     }
 
@@ -237,7 +295,8 @@
      * @param params Any parameters to include with the command
      * @param cb The callback to receive the result on
      */
-    public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
+    public void sendControlCommand(@NonNull String command, @Nullable Bundle params,
+            @Nullable ResultReceiver cb) {
         if (TextUtils.isEmpty(command)) {
             throw new IllegalArgumentException("command cannot be null or empty");
         }
@@ -287,12 +346,6 @@
     }
 
     private void addCallbackLocked(Callback cb, Handler handler) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        if (handler == null) {
-            throw new IllegalArgumentException("Handler cannot be null");
-        }
         if (getHandlerForCallbackLocked(cb) != null) {
             Log.w(TAG, "Callback is already added, ignoring");
             return;
@@ -305,23 +358,29 @@
                 mSessionBinder.registerCallbackListener(mCbStub);
                 mCbRegistered = true;
             } catch (RemoteException e) {
-                Log.d(TAG, "Dead object in registerCallback", e);
+                Log.e(TAG, "Dead object in registerCallback", e);
             }
         }
     }
 
     private boolean removeCallbackLocked(Callback cb) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
+        boolean success = false;
         for (int i = mCallbacks.size() - 1; i >= 0; i--) {
             MessageHandler handler = mCallbacks.get(i);
             if (cb == handler.mCallback) {
                 mCallbacks.remove(i);
-                return true;
+                success = true;
             }
         }
-        return false;
+        if (mCbRegistered && mCallbacks.size() == 0) {
+            try {
+                mSessionBinder.unregisterCallbackListener(mCbStub);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in removeCallbackLocked");
+            }
+            mCbRegistered = false;
+        }
+        return success;
     }
 
     private MessageHandler getHandlerForCallbackLocked(Callback cb) {
@@ -355,9 +414,10 @@
          * specified interface. Controllers should only handle these for
          * sessions they own.
          *
-         * @param event
+         * @param event The event from the session.
+         * @param extras Optional parameters for the event, may be null.
          */
-        public void onSessionEvent(String event, Bundle extras) {
+        public void onSessionEvent(@NonNull String event, @Nullable Bundle extras) {
         }
 
         /**
@@ -374,16 +434,24 @@
          *
          * @param state The new playback state of the session
          */
-        public void onPlaybackStateChanged(PlaybackState state) {
+        public void onPlaybackStateChanged(@NonNull PlaybackState state) {
         }
 
         /**
          * Override to handle changes to the current metadata.
          *
+         * @param metadata The current metadata for the session or null if none.
          * @see MediaMetadata
-         * @param metadata The current metadata for the session or null
          */
-        public void onMetadataChanged(MediaMetadata metadata) {
+        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+        }
+
+        /**
+         * Override to handle changes to the volume info.
+         *
+         * @param info The current volume info for this session.
+         */
+        public void onVolumeInfoChanged(VolumeInfo info) {
         }
     }
 
@@ -507,6 +575,85 @@
         }
     }
 
+    /**
+     * Holds information about the way volume is handled for this session.
+     */
+    public static final class VolumeInfo {
+        private final int mVolumeType;
+        private final int mAudioStream;
+        private final int mVolumeControl;
+        private final int mMaxVolume;
+        private final int mCurrentVolume;
+
+        /**
+         * @hide
+         */
+        public VolumeInfo(int type, int stream, int control, int max, int current) {
+            mVolumeType = type;
+            mAudioStream = stream;
+            mVolumeControl = control;
+            mMaxVolume = max;
+            mCurrentVolume = current;
+        }
+
+        /**
+         * Get the type of volume handling, either local or remote. One of:
+         * <ul>
+         * <li>{@link MediaSession#PLAYBACK_TYPE_LOCAL}</li>
+         * <li>{@link MediaSession#PLAYBACK_TYPE_REMOTE}</li>
+         * </ul>
+         *
+         * @return The type of volume handling this session is using.
+         */
+        public int getVolumeType() {
+            return mVolumeType;
+        }
+
+        /**
+         * Get the stream this is currently controlling volume on. When the volume
+         * type is {@link MediaSession#PLAYBACK_TYPE_REMOTE} this value does not
+         * have meaning and should be ignored.
+         *
+         * @return The stream this session is playing on.
+         */
+        public int getAudioStream() {
+            return mAudioStream;
+        }
+
+        /**
+         * Get the type of volume control that can be used. One of:
+         * <ul>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+         * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+         * </ul>
+         *
+         * @return The type of volume control that may be used with this
+         *         session.
+         */
+        public int getVolumeControl() {
+            return mVolumeControl;
+        }
+
+        /**
+         * Get the maximum volume that may be set for this session.
+         *
+         * @return The maximum allowed volume where this session is playing.
+         */
+        public int getMaxVolume() {
+            return mMaxVolume;
+        }
+
+        /**
+         * Get the current volume for this session.
+         *
+         * @return The current volume where this session is playing.
+         */
+        public int getCurrentVolume() {
+            return mCurrentVolume;
+        }
+    }
+
     private final static class CallbackStub extends ISessionControllerCallback.Stub {
         private final WeakReference<MediaController> mController;
 
@@ -546,6 +693,16 @@
             }
         }
 
+        @Override
+        public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
+            MediaController controller = mController.get();
+            if (controller != null) {
+                VolumeInfo info = new VolumeInfo(pvi.volumeType, pvi.audioStream, pvi.controlType,
+                        pvi.maxVolume, pvi.currentVolume);
+                controller.postMessage(MSG_UPDATE_VOLUME, info, null);
+            }
+        }
+
     }
 
     private final static class MessageHandler extends Handler {
@@ -571,6 +728,9 @@
                 case MSG_UPDATE_METADATA:
                     mCallback.onMetadataChanged((MediaMetadata) msg.obj);
                     break;
+                case MSG_UPDATE_VOLUME:
+                    mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
+                    break;
             }
         }
 
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4ba1351..7637ec8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,10 +19,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
 import android.media.session.ISessionController;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -49,10 +51,9 @@
  * for all playback, though multiple sessions can be created to provide finer
  * grain controls of media.
  * <p>
- * A MediaSession is created by calling
- * {@link MediaSessionManager#createSession(String)}. Once a session is created
- * the owner of the session may use {@link #getSessionToken()} to allow apps to
- * create a {@link MediaController} to interact with this session.
+ * Once a session is created the owner of the session may pass its
+ * {@link #getSessionToken() session token} to other processes to allow them to
+ * create a {@link MediaController} to interact with the session.
  * <p>
  * To receive commands, media keys, and other events a {@link Callback} must be
  * set with {@link #addCallback(Callback)}. To receive transport control
@@ -62,10 +63,10 @@
  * When an app is finished performing playback it must call {@link #release()}
  * to clean up the session and notify any controllers.
  * <p>
- * MediaSession objects are thread safe
+ * MediaSession objects are thread safe.
  */
 public final class MediaSession {
-    private static final String TAG = "Session";
+    private static final String TAG = "MediaSession";
 
     /**
      * Set this flag on the session to indicate that it can handle media button
@@ -75,9 +76,8 @@
 
     /**
      * Set this flag on the session to indicate that it handles transport
-     * control commands through a {@link TransportControlsCallback}. The
-     * callback can be retrieved by calling
-     * {@link #addTransportControlsCallback}.
+     * control commands through a {@link TransportControlsCallback}.
+     * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
      */
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
@@ -125,20 +125,14 @@
     public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
 
     /**
-     * The session uses local playback. Used for configuring volume handling
-     * with the system.
-     *
-     * @hide
+     * The session uses local playback.
      */
-    public static final int VOLUME_TYPE_LOCAL = 1;
+    public static final int PLAYBACK_TYPE_LOCAL = 1;
 
     /**
-     * The session uses remote playback. Used for configuring volume handling
-     * with the system.
-     *
-     * @hide
+     * The session uses remote playback.
      */
-    public static final int VOLUME_TYPE_REMOTE = 2;
+    public static final int PLAYBACK_TYPE_REMOTE = 2;
 
     private final Object mLock = new Object();
 
@@ -155,9 +149,9 @@
             = new ArrayMap<String, RouteInterface.EventListener>();
 
     private Route mRoute;
-    private RemoteVolumeProvider mVolumeProvider;
+    private VolumeProvider mVolumeProvider;
 
-    private boolean mActive = false;;
+    private boolean mActive = false;
 
     /**
      * @hide
@@ -181,7 +175,7 @@
      *
      * @param callback The callback object
      */
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         addCallback(callback, null);
     }
 
@@ -192,7 +186,7 @@
      * @param callback The callback to receive updates on.
      * @param handler The handler that events should be posted on.
      */
-    public void addCallback(Callback callback, Handler handler) {
+    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
         if (callback == null) {
             throw new IllegalArgumentException("Callback cannot be null");
         }
@@ -215,7 +209,7 @@
      *
      * @param callback The callback to remove.
      */
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         synchronized (mLock) {
             removeCallbackLocked(callback);
         }
@@ -227,11 +221,26 @@
      *
      * @param pi The intent to launch to show UI for this Session.
      */
-    public void setLaunchPendingIntent(PendingIntent pi) {
+    public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
         // TODO
     }
 
     /**
+     * Set a media button event receiver component to use to restart playback
+     * after an app has been stopped.
+     *
+     * @param mbr The receiver component to send the media button event to.
+     * @hide
+     */
+    public void setMediaButtonReceiver(@Nullable ComponentName mbr) {
+        try {
+            mBinder.setMediaButtonReceiver(mbr);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+        }
+    }
+
+    /**
      * Set any flags for the session.
      *
      * @param flags The flags to set for this session.
@@ -256,7 +265,7 @@
      */
     public void setPlaybackToLocal(int stream) {
         try {
-            mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
+            mBinder.configureVolumeHandling(PLAYBACK_TYPE_LOCAL, stream, 0);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
         }
@@ -272,15 +281,20 @@
      * @param volumeProvider The provider that will handle volume changes. May
      *            not be null.
      */
-    public void setPlaybackToRemote(RemoteVolumeProvider volumeProvider) {
+    public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
         if (volumeProvider == null) {
             throw new IllegalArgumentException("volumeProvider may not be null!");
         }
         mVolumeProvider = volumeProvider;
-        volumeProvider.setSession(this);
+        volumeProvider.setCallback(new VolumeProvider.Callback() {
+            @Override
+            public void onVolumeChanged(VolumeProvider volumeProvider) {
+                notifyRemoteVolumeChanged(volumeProvider);
+            }
+        });
 
         try {
-            mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
+            mBinder.configureVolumeHandling(PLAYBACK_TYPE_REMOTE, volumeProvider.getVolumeControl(),
                     volumeProvider.getMaxVolume());
         } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
@@ -324,7 +338,7 @@
      * @param event The name of the event to send
      * @param extras Any extras included with the event
      */
-    public void sendSessionEvent(String event, Bundle extras) {
+    public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
         if (TextUtils.isEmpty(event)) {
             throw new IllegalArgumentException("event cannot be null or empty");
         }
@@ -356,7 +370,7 @@
      * @return A token that can be used to create a MediaController for this
      *         session
      */
-    public MediaSessionToken getSessionToken() {
+    public @NonNull MediaSessionToken getSessionToken() {
         return mSessionToken;
     }
 
@@ -501,7 +515,7 @@
      *
      * @param state The current state of playback
      */
-    public void setPlaybackState(PlaybackState state) {
+    public void setPlaybackState(@Nullable PlaybackState state) {
         try {
             mBinder.setPlaybackState(state);
         } catch (RemoteException e) {
@@ -515,7 +529,7 @@
      *
      * @param metadata The new metadata
      */
-    public void setMetadata(MediaMetadata metadata) {
+    public void setMetadata(@Nullable MediaMetadata metadata) {
         try {
             mBinder.setMetadata(metadata);
         } catch (RemoteException e) {
@@ -524,12 +538,12 @@
     }
 
     /**
-     * Notify the system that the remove volume changed.
+     * Notify the system that the remote volume changed.
      *
      * @param provider The provider that is handling volume changes.
      * @hide
      */
-    void notifyRemoteVolumeChanged(RemoteVolumeProvider provider) {
+    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
         if (provider == null || provider != mVolumeProvider) {
             Log.w(TAG, "Received update from stale volume provider");
             return;
@@ -703,7 +717,7 @@
          * @param mediaButtonIntent an intent containing the KeyEvent as an
          *            extra
          */
-        public void onMediaButtonEvent(Intent mediaButtonIntent) {
+        public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
         }
 
         /**
@@ -711,10 +725,12 @@
          * The owner of the session may handle custom commands but is not
          * required to.
          *
-         * @param command
-         * @param extras optional
+         * @param command The command name.
+         * @param extras Optional parameters for the command, may be null.
+         * @param cb A result receiver to which a result may be sent by the command, may be null.
          */
-        public void onControlCommand(String command, Bundle extras, ResultReceiver cb) {
+        public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
+                @Nullable ResultReceiver cb) {
         }
 
         /**
@@ -820,7 +836,7 @@
          *
          * @param rating
          */
-        public void onSetRating(Rating rating) {
+        public void onSetRating(@NonNull Rating rating) {
         }
 
         /**
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 801844f..838b857 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.media.MediaMetadata;
@@ -214,7 +215,7 @@
         }
     }
 
-    public void addMediaButtonListener(PendingIntent pi,
+    public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
             Context context) {
         if (pi == null) {
             Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
@@ -238,6 +239,7 @@
 
         holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
         holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
+        holder.mSession.setMediaButtonReceiver(mbrComponent);
         if (DEBUG) {
             Log.d(TAG, "addMediaButtonListener added " + pi);
         }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 2e6b86e..9291bb0 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -16,14 +16,18 @@
 
 package android.media.session;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.media.IRemoteVolumeController;
 import android.media.session.ISessionManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -63,10 +67,10 @@
     /**
      * Creates a new session.
      *
-     * @param tag A short name for debugging purposes
-     * @return a {@link MediaSession} for the new session
+     * @param tag A short name for debugging purposes.
+     * @return A {@link MediaSession} for the new session.
      */
-    public MediaSession createSession(String tag) {
+    public @NonNull MediaSession createSession(@NonNull String tag) {
         return createSessionAsUser(tag, UserHandle.myUserId());
     }
 
@@ -76,12 +80,16 @@
      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
      * permission.
      *
-     * @param tag A short name for debugging purposes
+     * @param tag A short name for debugging purposes.
      * @param userId The user id to create the session as.
-     * @return a {@link MediaSession} for the new session
+     * @return A {@link MediaSession} for the new session.
      * @hide
      */
-    public MediaSession createSessionAsUser(String tag, int userId) {
+    public @NonNull MediaSession createSessionAsUser(@NonNull String tag, int userId) {
+        if (TextUtils.isEmpty(tag)) {
+            throw new IllegalArgumentException("tag must not be null or empty");
+        }
+
         try {
             MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
             MediaSession session = new MediaSession(mService
@@ -108,10 +116,10 @@
      *
      * @param notificationListener The enabled notification listener component.
      *            May be null.
-     * @return A list of controllers for ongoing sessions
-     * @hide
+     * @return A list of controllers for ongoing sessions.
      */
-    public List<MediaController> getActiveSessions(ComponentName notificationListener) {
+    public @NonNull List<MediaController> getActiveSessions(
+            @Nullable ComponentName notificationListener) {
         return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
     }
 
@@ -128,8 +136,8 @@
      * @return A list of controllers for ongoing sessions.
      * @hide
      */
-    public List<MediaController> getActiveSessionsForUser(ComponentName notificationListener,
-            int userId) {
+    public @NonNull List<MediaController> getActiveSessionsForUser(
+            @Nullable ComponentName notificationListener, int userId) {
         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
         try {
             List<IBinder> binders = mService.getSessions(notificationListener, userId);
@@ -157,11 +165,29 @@
      * @param sessionListener The listener to add.
      * @param notificationListener The enabled notification listener component.
      *            May be null.
+     */
+    public void addActiveSessionsListener(SessionListener sessionListener,
+            ComponentName notificationListener) {
+        addActiveSessionsListener(sessionListener, notificationListener, UserHandle.myUserId());
+    }
+
+    /**
+     * Add a listener to be notified when the list of active sessions
+     * changes.This requires the
+     * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
+     * the calling app. You may also retrieve this list if your app is an
+     * enabled notification listener using the
+     * {@link NotificationListenerService} APIs, in which case you must pass the
+     * {@link ComponentName} of your enabled listener.
+     *
+     * @param sessionListener The listener to add.
+     * @param notificationListener The enabled notification listener component.
+     *            May be null.
      * @param userId The userId to listen for changes on.
      * @hide
      */
-    public void addActiveSessionsListener(SessionListener sessionListener,
-            ComponentName notificationListener, int userId) {
+    public void addActiveSessionsListener(@NonNull SessionListener sessionListener,
+            @Nullable ComponentName notificationListener, int userId) {
         if (sessionListener == null) {
             throw new IllegalArgumentException("listener may not be null");
         }
@@ -178,7 +204,7 @@
      * @param listener The listener to remove.
      * @hide
      */
-    public void removeActiveSessionsListener(SessionListener listener) {
+    public void removeActiveSessionsListener(@NonNull SessionListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener may not be null");
         }
@@ -190,24 +216,38 @@
     }
 
     /**
+     * Set the remote volume controller to receive volume updates on. Only for
+     * use by system UI.
+     *
+     * @param rvc The volume controller to receive updates on.
+     * @hide
+     */
+    public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+        try {
+            mService.setRemoteVolumeController(rvc);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in setRemoteVolumeController.", e);
+        }
+    }
+
+    /**
      * Send a media key event. The receiver will be selected automatically.
      *
      * @param keyEvent The KeyEvent to send.
      * @hide
      */
-    public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
+    public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
         dispatchMediaKeyEvent(keyEvent, false);
     }
 
     /**
      * Send a media key event. The receiver will be selected automatically.
      *
-     * @param keyEvent The KeyEvent to send
-     * @param needWakeLock true if a wake lock should be held while sending the
-     *            key
+     * @param keyEvent The KeyEvent to send.
+     * @param needWakeLock True if a wake lock should be held while sending the key.
      * @hide
      */
-    public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+    public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
         try {
             mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
         } catch (RemoteException e) {
@@ -236,8 +276,6 @@
     /**
      * Listens for changes to the list of active sessions. This can be added
      * using {@link #addActiveSessionsListener}.
-     *
-     * @hide
      */
     public static abstract class SessionListener {
         /**
@@ -249,7 +287,8 @@
          * @param controllers The updated list of controllers for the user that
          *            changed.
          */
-        public abstract void onActiveSessionsChanged(List<MediaController> controllers);
+        public abstract void onActiveSessionsChanged(
+                @Nullable List<MediaController> controllers);
 
         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
             @Override
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.aidl b/media/java/android/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..c4250f0
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,18 @@
+/* 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.
+*/
+
+package android.media.session;
+
+parcelable ParcelableVolumeInfo;
diff --git a/media/java/android/media/session/ParcelableVolumeInfo.java b/media/java/android/media/session/ParcelableVolumeInfo.java
new file mode 100644
index 0000000..166ccd3
--- /dev/null
+++ b/media/java/android/media/session/ParcelableVolumeInfo.java
@@ -0,0 +1,78 @@
+/* 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.
+ */
+
+package android.media.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class for passing information about the audio configuration of a
+ * session. The public implementation is {@link MediaController.VolumeInfo}.
+ *
+ * @hide
+ */
+public class ParcelableVolumeInfo implements Parcelable {
+    public int volumeType;
+    public int audioStream;
+    public int controlType;
+    public int maxVolume;
+    public int currentVolume;
+
+    public ParcelableVolumeInfo(int volumeType, int audioStream, int controlType, int maxVolume,
+            int currentVolume) {
+        this.volumeType = volumeType;
+        this.audioStream = audioStream;
+        this.controlType = controlType;
+        this.maxVolume = maxVolume;
+        this.currentVolume = currentVolume;
+    }
+
+    public ParcelableVolumeInfo(Parcel from) {
+        volumeType = from.readInt();
+        audioStream = from.readInt();
+        controlType = from.readInt();
+        maxVolume = from.readInt();
+        currentVolume = from.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(volumeType);
+        dest.writeInt(audioStream);
+        dest.writeInt(controlType);
+        dest.writeInt(maxVolume);
+        dest.writeInt(currentVolume);
+    }
+
+
+    public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
+            = new Parcelable.Creator<ParcelableVolumeInfo>() {
+        @Override
+        public ParcelableVolumeInfo createFromParcel(Parcel in) {
+            return new ParcelableVolumeInfo(in);
+        }
+
+        @Override
+        public ParcelableVolumeInfo[] newArray(int size) {
+            return new ParcelableVolumeInfo[size];
+        }
+    };
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 3b3f249..6125cb4 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -561,8 +561,8 @@
         return 0;
     }
 
-    public static final Parcelable.Creator<PlaybackState> CREATOR
-            = new Parcelable.Creator<PlaybackState>() {
+    public static final Parcelable.Creator<PlaybackState> CREATOR =
+            new Parcelable.Creator<PlaybackState>() {
         @Override
         public PlaybackState createFromParcel(Parcel in) {
             return new PlaybackState(in);
diff --git a/media/java/android/media/session/RemoteVolumeProvider.java b/media/java/android/media/session/RemoteVolumeProvider.java
deleted file mode 100644
index 606b1d7..0000000
--- a/media/java/android/media/session/RemoteVolumeProvider.java
+++ /dev/null
@@ -1,123 +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 android.media.session;
-
-import android.os.RemoteException;
-import android.util.Log;
-
-/**
- * Handles requests to adjust or set the volume on a session. This is also used
- * to push volume updates back to the session after a request has been handled.
- * You can set a volume provider on a session by calling
- * {@link MediaSession#setPlaybackToRemote}.
- */
-public abstract class RemoteVolumeProvider {
-    private static final String TAG = "RemoteVolumeProvider";
-
-    /**
-     * The volume is fixed and can not be modified. Requests to change volume
-     * should be ignored.
-     */
-    public static final int VOLUME_CONTROL_FIXED = 0;
-
-    /**
-     * The volume control uses relative adjustment via
-     * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
-     * value should be ignored.
-     */
-    public static final int VOLUME_CONTROL_RELATIVE = 1;
-
-    /**
-     * The volume control uses an absolute value. It may be adjusted using
-     * {@link #onAdjustVolumeBy(int)} or set directly using
-     * {@link #onSetVolumeTo(int)}.
-     */
-    public static final int VOLUME_CONTROL_ABSOLUTE = 2;
-
-    private final int mControlType;
-    private final int mMaxVolume;
-
-    private MediaSession mSession;
-
-    /**
-     * Create a new volume provider for handling volume events. You must specify
-     * the type of volume control and the maximum volume that can be used.
-     *
-     * @param volumeControl The method for controlling volume that is used by
-     *            this provider.
-     * @param maxVolume The maximum allowed volume.
-     */
-    public RemoteVolumeProvider(int volumeControl, int maxVolume) {
-        mControlType = volumeControl;
-        mMaxVolume = maxVolume;
-    }
-
-    /**
-     * Get the current volume of the remote playback.
-     *
-     * @return The current volume.
-     */
-    public abstract int onGetCurrentVolume();
-
-    /**
-     * Get the volume control type that this volume provider uses.
-     *
-     * @return The volume control type for this volume provider
-     */
-    public final int getVolumeControl() {
-        return mControlType;
-    }
-
-    /**
-     * Get the maximum volume this provider allows.
-     *
-     * @return The max allowed volume.
-     */
-    public final int getMaxVolume() {
-        return mMaxVolume;
-    }
-
-    /**
-     * Notify the system that the remote playback's volume has been changed.
-     */
-    public final void notifyVolumeChanged() {
-        mSession.notifyRemoteVolumeChanged(this);
-    }
-
-    /**
-     * Override to handle requests to set the volume of the current output.
-     *
-     * @param volume The volume to set the output to.
-     */
-    public void onSetVolumeTo(int volume) {
-    }
-
-    /**
-     * Override to handle requests to adjust the volume of the current
-     * output.
-     *
-     * @param delta The amount to change the volume
-     */
-    public void onAdjustVolumeBy(int delta) {
-    }
-
-    /**
-     * @hide
-     */
-    void setSession(MediaSession session) {
-        mSession = session;
-    }
-}
\ No newline at end of file
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 52045d3..7e9d279 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -22,7 +22,9 @@
 import android.net.Uri;
 import android.provider.BaseColumns;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * <p>
@@ -144,15 +146,41 @@
     /**
      * Builds a URI that points to all programs on a given channel.
      *
+     * @param channelId The ID of the channel to return programs for.
+     */
+    public static final Uri buildProgramsUriForChannel(long channelId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+                .appendPath(PATH_CHANNEL).appendPath(String.valueOf(channelId))
+                .appendPath(PATH_PROGRAM).build();
+    }
+
+    /**
+     * Builds a URI that points to all programs on a given channel.
+     *
      * @param channelUri The URI of the channel to return programs for.
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri) {
         if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
-        String channelId = String.valueOf(ContentUris.parseId(channelUri));
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build();
+        return buildProgramsUriForChannel(ContentUris.parseId(channelUri));
+    }
+
+    /**
+     * Builds a URI that points to programs on a specific channel whose schedules overlap with the
+     * given time frame.
+     *
+     * @param channelId The ID of the channel to return programs for.
+     * @param startTime The start time used to filter programs. The returned programs should have
+     *            {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time.
+     * @param endTime The end time used to filter programs. The returned programs should have
+     *            {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
+     */
+    public static final Uri buildProgramsUriForChannel(long channelId, long startTime,
+            long endTime) {
+        Uri uri = buildProgramsUriForChannel(channelId);
+        return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
+                .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
     }
 
     /**
@@ -167,9 +195,10 @@
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
             long endTime) {
-        Uri uri = buildProgramsUriForChannel(channelUri);
-        return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
-                .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
+        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+            throw new IllegalArgumentException("Not a channel: " + channelUri);
+        }
+        return buildProgramsUriForChannel(ContentUris.parseId(channelUri), startTime, endTime);
     }
 
     /**
@@ -272,6 +301,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;
 
@@ -344,6 +382,81 @@
         /** The service type for radio channels that have audio only. */
         public static final int SERVICE_TYPE_AUDIO = 0x2;
 
+        /** The video format for 240p. */
+        public static final String VIDEO_FORMAT_240P = "VIDEO_FORMAT_240P";
+
+        /** The video format for 360p. */
+        public static final String VIDEO_FORMAT_360P = "VIDEO_FORMAT_360P";
+
+        /** The video format for 480i. */
+        public static final String VIDEO_FORMAT_480I = "VIDEO_FORMAT_480I";
+
+        /** The video format for 480p. */
+        public static final String VIDEO_FORMAT_480P = "VIDEO_FORMAT_480P";
+
+        /** The video format for 576i. */
+        public static final String VIDEO_FORMAT_576I = "VIDEO_FORMAT_576I";
+
+        /** The video format for 576p. */
+        public static final String VIDEO_FORMAT_576P = "VIDEO_FORMAT_576P";
+
+        /** The video format for 720p. */
+        public static final String VIDEO_FORMAT_720P = "VIDEO_FORMAT_720P";
+
+        /** The video format for 1080i. */
+        public static final String VIDEO_FORMAT_1080I = "VIDEO_FORMAT_1080I";
+
+        /** The video format for 1080p. */
+        public static final String VIDEO_FORMAT_1080P = "VIDEO_FORMAT_1080P";
+
+        /** The video format for 2160p. */
+        public static final String VIDEO_FORMAT_2160P = "VIDEO_FORMAT_2160P";
+
+        /** The video format for 4320p. */
+        public static final String VIDEO_FORMAT_4320P = "VIDEO_FORMAT_4320P";
+
+        /** The video resolution for standard-definition. */
+        public static final String VIDEO_RESOLUTION_SD = "VIDEO_RESOLUTION_SD";
+
+        /** The video resolution for enhanced-definition. */
+        public static final String VIDEO_RESOLUTION_ED = "VIDEO_RESOLUTION_ED";
+
+        /** The video resolution for high-definition. */
+        public static final String VIDEO_RESOLUTION_HD = "VIDEO_RESOLUTION_HD";
+
+        /** The video resolution for full high-definition. */
+        public static final String VIDEO_RESOLUTION_FHD = "VIDEO_RESOLUTION_FHD";
+
+        /** The video resolution for ultra high-definition. */
+        public static final String VIDEO_RESOLUTION_UHD = "VIDEO_RESOLUTION_UHD";
+
+        private static final Map<String, String> VIDEO_FORMAT_TO_RESOLUTION_MAP =
+                new HashMap<String, String>();
+
+        static {
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_480I, VIDEO_RESOLUTION_SD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_480P, VIDEO_RESOLUTION_ED);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_576I, VIDEO_RESOLUTION_SD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_576P, VIDEO_RESOLUTION_ED);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_720P, VIDEO_RESOLUTION_HD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_1080I, VIDEO_RESOLUTION_HD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_1080P, VIDEO_RESOLUTION_FHD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_2160P, VIDEO_RESOLUTION_UHD);
+            VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_4320P, VIDEO_RESOLUTION_UHD);
+        }
+
+        /**
+         * Returns the video resolution (definition) for a given video format.
+         *
+         * @param videoFormat The video format defined in {@link Channels}.
+         * @return the corresponding video resolution string. {@code null} if the resolution string
+         *         is not defined for the given video format.
+         * @see #COLUMN_VIDEO_FORMAT
+         */
+        public static final String getVideoResolution(String videoFormat) {
+            return VIDEO_FORMAT_TO_RESOLUTION_MAP.get(videoFormat);
+        }
+
         /**
          * The name of the {@link TvInputService} subclass that provides this TV channel. This
          * should be a fully qualified class name (such as, "com.example.project.TvInputService").
@@ -477,6 +590,24 @@
         public static final String COLUMN_DESCRIPTION = "description";
 
         /**
+         * The typical video format for programs from this TV channel.
+         * <p>
+         * This is primarily used to filter out channels based on video format by applications. The
+         * value should match one of the followings: {@link #VIDEO_FORMAT_240P},
+         * {@link #VIDEO_FORMAT_360P}, {@link #VIDEO_FORMAT_480I}, {@link #VIDEO_FORMAT_480P},
+         * {@link #VIDEO_FORMAT_576I}, {@link #VIDEO_FORMAT_576P}, {@link #VIDEO_FORMAT_720P},
+         * {@link #VIDEO_FORMAT_1080I}, {@link #VIDEO_FORMAT_1080P}, {@link #VIDEO_FORMAT_2160P},
+         * {@link #VIDEO_FORMAT_4320P}. Note that the actual video resolution of each program from a
+         * given channel can vary thus one should use {@link Programs#COLUMN_VIDEO_WIDTH} and
+         * {@link Programs#COLUMN_VIDEO_HEIGHT} to get more accurate video resolution.
+         * </p><p>
+         * Type: TEXT
+         * </p><p>
+         * @see #getVideoResolution
+         */
+        public static final String COLUMN_VIDEO_FORMAT = "video_format";
+
+        /**
          * The flag indicating whether this TV channel is browsable or not.
          * <p>
          * A value of 1 indicates the channel is included in the channel list that applications use
@@ -683,6 +814,32 @@
         public static final String COLUMN_LONG_DESCRIPTION = "long_description";
 
         /**
+         * The width of the video for this TV program, in the unit of pixels.
+         * <p>
+         * Together with {@link #COLUMN_VIDEO_HEIGHT} this is used to determine the video resolution
+         * of the current TV program. Can be empty if it is not known initially or the program does
+         * not convey any video such as the programs from type {@link Channels#SERVICE_TYPE_AUDIO}
+         * channels.
+         * </p><p>
+         * Type: INTEGER
+         * </p>
+         */
+        public static final String COLUMN_VIDEO_WIDTH = "video_width";
+
+        /**
+         * The height of the video for this TV program, in the unit of pixels.
+         * <p>
+         * Together with {@link #COLUMN_VIDEO_WIDTH} this is used to determine the video resolution
+         * of the current TV program. Can be empty if it is not known initially or the program does
+         * not convey any video such as the programs from type {@link Channels#SERVICE_TYPE_AUDIO}
+         * channels.
+         * </p><p>
+         * Type: INTEGER
+         * </p>
+         */
+        public static final String COLUMN_VIDEO_HEIGHT = "video_height";
+
+        /**
          * The comma-separated audio languages of this TV program.
          * <p>
          * This is used to describe available audio languages included in the program. Use
@@ -742,37 +899,37 @@
         /** Canonical genres for TV programs. */
         public static final class Genres {
             /** The genre for Family/Kids. */
-            public static final String FAMILY_KIDS = "Family/Kids";
+            public static final String FAMILY_KIDS = "FAMILY_KIDS";
 
             /** The genre for Sports. */
-            public static final String SPORTS = "Sports";
+            public static final String SPORTS = "SPORTS";
 
             /** The genre for Shopping. */
-            public static final String SHOPPING = "Shopping";
+            public static final String SHOPPING = "SHOPPING";
 
             /** The genre for Movies. */
-            public static final String MOVIES = "Movies";
+            public static final String MOVIES = "MOVIES";
 
             /** The genre for Comedy. */
-            public static final String COMEDY = "Comedy";
+            public static final String COMEDY = "COMEDY";
 
             /** The genre for Travel. */
-            public static final String TRAVEL = "Travel";
+            public static final String TRAVEL = "TRAVEL";
 
             /** The genre for Drama. */
-            public static final String DRAMA = "Drama";
+            public static final String DRAMA = "DRAMA";
 
             /** The genre for Education. */
-            public static final String EDUCATION = "Education";
+            public static final String EDUCATION = "EDUCATION";
 
             /** The genre for Animal/Wildlife. */
-            public static final String ANIMAL_WILDLIFE = "Animal/Wildlife";
+            public static final String ANIMAL_WILDLIFE = "ANIMAL_WILDLIFE";
 
             /** The genre for News. */
-            public static final String NEWS = "News";
+            public static final String NEWS = "NEWS";
 
             /** The genre for Gaming. */
-            public static final String GAMING = "Gaming";
+            public static final String GAMING = "GAMING";
 
             private Genres() {}
 
@@ -812,7 +969,7 @@
      *
      * @hide
      */
-    public static final class WatchedPrograms implements BaseColumns {
+    public static final class WatchedPrograms implements BaseTvColumns {
 
         /** The content:// style URI for this table. */
         public static final Uri CONTENT_URI =
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 4beb960..e5f9889 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.media.AudioManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
@@ -56,14 +57,11 @@
 
     private int mDeviceId;
     private int mType;
-    // TODO: Add audio port & audio address for audio service.
-    // TODO: Add HDMI handle for HDMI service.
+    private int mAudioType;
+    private String mAudioAddress;
+    private int mHdmiPortId;
 
-    public TvInputHardwareInfo() { }
-
-    public TvInputHardwareInfo(int deviceId, int type) {
-        mDeviceId = deviceId;
-        mType = type;
+    private TvInputHardwareInfo() {
     }
 
     public int getDeviceId() {
@@ -74,6 +72,21 @@
         return mType;
     }
 
+    public int getAudioType() {
+        return mAudioType;
+    }
+
+    public String getAudioAddress() {
+        return mAudioAddress;
+    }
+
+    public int getHdmiPortId() {
+        if (mType != TV_INPUT_TYPE_HDMI) {
+            throw new IllegalStateException();
+        }
+        return mHdmiPortId;
+    }
+
     // Parcelable
     @Override
     public int describeContents() {
@@ -84,10 +97,78 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mDeviceId);
         dest.writeInt(mType);
+        dest.writeInt(mAudioType);
+        dest.writeString(mAudioAddress);
+        if (mType == TV_INPUT_TYPE_HDMI) {
+            dest.writeInt(mHdmiPortId);
+        }
     }
 
     public void readFromParcel(Parcel source) {
         mDeviceId = source.readInt();
         mType = source.readInt();
+        mAudioType = source.readInt();
+        mAudioAddress = source.readString();
+        if (mType == TV_INPUT_TYPE_HDMI) {
+            mHdmiPortId = source.readInt();
+        }
+    }
+
+    public static final class Builder {
+        private Integer mDeviceId = null;
+        private Integer mType = null;
+        private int mAudioType = AudioManager.DEVICE_NONE;
+        private String mAudioAddress = "";
+        private Integer mHdmiPortId = null;
+
+        public Builder() {
+        }
+
+        public Builder deviceId(int deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
+        public Builder type(int type) {
+            mType = type;
+            return this;
+        }
+
+        public Builder audioType(int audioType) {
+            mAudioType = audioType;
+            return this;
+        }
+
+        public Builder audioAddress(String audioAddress) {
+            mAudioAddress = audioAddress;
+            return this;
+        }
+
+        public Builder hdmiPortId(int hdmiPortId) {
+            mHdmiPortId = hdmiPortId;
+            return this;
+        }
+
+        public TvInputHardwareInfo build() {
+            if (mDeviceId == null || mType == null) {
+                throw new UnsupportedOperationException();
+            }
+            if ((mType == TV_INPUT_TYPE_HDMI && mHdmiPortId == null) ||
+                    (mType != TV_INPUT_TYPE_HDMI && mHdmiPortId != null)) {
+                throw new UnsupportedOperationException();
+            }
+
+            TvInputHardwareInfo info = new TvInputHardwareInfo();
+            info.mDeviceId = mDeviceId;
+            info.mType = mType;
+            info.mAudioType = mAudioType;
+            if (info.mAudioType != AudioManager.DEVICE_NONE) {
+                info.mAudioAddress = mAudioAddress;
+            }
+            if (mHdmiPortId != null) {
+                info.mHdmiPortId = mHdmiPortId;
+            }
+            return info;
+        }
     }
 }
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/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index edfdd60..daa7009 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -533,12 +533,11 @@
 
         /**
          * Releases this session.
-         *
-         * @throws IllegalStateException if the session has been already released.
          */
         public void release() {
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 mService.releaseSession(mToken, mUserId);
@@ -553,12 +552,12 @@
          * Sets the {@link android.view.Surface} for this session.
          *
          * @param surface A {@link android.view.Surface} used to render video.
-         * @throws IllegalStateException if the session has been already released.
          * @hide
          */
         public void setSurface(Surface surface) {
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             // surface can be null.
             try {
@@ -573,11 +572,11 @@
          *
          * @param volume A volume value between 0.0f to 1.0f.
          * @throws IllegalArgumentException if the volume value is out of range.
-         * @throws IllegalStateException if the session has been already released.
          */
         public void setStreamVolume(float volume) {
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 if (volume < 0.0f || volume > 1.0f) {
@@ -594,14 +593,14 @@
          *
          * @param channelUri The URI of a channel.
          * @throws IllegalArgumentException if the argument is {@code null}.
-         * @throws IllegalStateException if the session has been already released.
          */
         public void tune(Uri channelUri) {
             if (channelUri == null) {
                 throw new IllegalArgumentException("channelUri cannot be null");
             }
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 mService.tune(mToken, channelUri, mUserId);
@@ -620,8 +619,7 @@
          * @param view A view playing TV.
          * @param frame A position of the overlay view.
          * @throws IllegalArgumentException if any of the arguments is {@code null}.
-         * @throws IllegalStateException if {@code view} is not attached to a window or
-         *         if the session has been already released.
+         * @throws IllegalStateException if {@code view} is not attached to a window.
          */
         void createOverlayView(View view, Rect frame) {
             if (view == null) {
@@ -634,7 +632,8 @@
                 throw new IllegalStateException("view must be attached to a window");
             }
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
@@ -648,14 +647,14 @@
          *
          * @param frame A new position of the overlay view.
          * @throws IllegalArgumentException if the arguments is {@code null}.
-         * @throws IllegalStateException if the session has been already released.
          */
         void relayoutOverlayView(Rect frame) {
             if (frame == null) {
                 throw new IllegalArgumentException("frame cannot be null");
             }
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 mService.relayoutOverlayView(mToken, frame, mUserId);
@@ -666,12 +665,11 @@
 
         /**
          * Removes the current overlay view.
-         *
-         * @throws IllegalStateException if the session has been already released.
          */
         void removeOverlayView() {
             if (mToken == null) {
-                throw new IllegalStateException("the session has been already released");
+                Log.w(TAG, "The session has been already released");
+                return;
             }
             try {
                 mService.removeOverlayView(mToken, mUserId);
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 409a33c..0f4a930 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -560,8 +560,11 @@
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+            boolean isNavigationKey = false;
             if (event instanceof KeyEvent) {
-                if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
+                KeyEvent keyEvent = (KeyEvent) event;
+                isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
+                if (keyEvent.dispatch(this, mDispatcherState, this)) {
                     return TvInputManager.Session.DISPATCH_HANDLED;
                 }
             } else if (event instanceof MotionEvent) {
@@ -587,8 +590,18 @@
             if (!mOverlayView.hasWindowFocus()) {
                 mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
             }
-            mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
-            return TvInputManager.Session.DISPATCH_IN_PROGRESS;
+            if (isNavigationKey && mOverlayView.hasFocusable()) {
+                // If mOverlayView has focusable views, navigation key events should be always
+                // handled. If not, it can make the application UI navigation messed up.
+                // For example, in the case that the left-most view is focused, a left key event
+                // will not be handled in ViewRootImpl. Then, the left key event will be handled in
+                // the application during the UI navigation of the TV input.
+                mOverlayView.getViewRootImpl().dispatchInputEvent(event);
+                return TvInputManager.Session.DISPATCH_HANDLED;
+            } else {
+                mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+                return TvInputManager.Session.DISPATCH_IN_PROGRESS;
+            }
         }
 
         private void setSessionCallback(ITvInputSessionCallback callback) {
@@ -596,6 +609,26 @@
         }
     }
 
+    /** @hide */
+    public static boolean isNavigationKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_PAGE_UP:
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+            case KeyEvent.KEYCODE_MOVE_HOME:
+            case KeyEvent.KEYCODE_MOVE_END:
+            case KeyEvent.KEYCODE_TAB:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_ENTER:
+                return true;
+        }
+        return false;
+    }
+
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
         private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 2831d9e..a913e59c 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -33,6 +33,7 @@
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 
@@ -339,6 +340,11 @@
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
         mSurfaceView.setVisibility(visibility);
+        if (visibility == View.VISIBLE) {
+            createSessionOverlayView();
+        } else {
+            removeSessionOverlayView();
+        }
     }
 
     private void release() {
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 36cfb0f..ad7ee7a 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -33,6 +33,9 @@
 #include <jni.h>
 #include <JNIHelp.h>
 
+#include <stdint.h>
+#include <inttypes.h>
+
 #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
 
 #define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID       "mNativeContext"
@@ -300,6 +303,14 @@
 
     // failed to find size, default to whole buffer
     if (size == 0) {
+        /*
+         * This is a problem because not including the JPEG header
+         * means that in certain rare situations a regular JPEG blob
+         * will be misidentified as having a header, in which case
+         * we will get a garbage size value.
+         */
+        ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
+                __FUNCTION__, width);
         size = width;
     }
 
@@ -767,11 +778,12 @@
     int imgReaderFmt = ctx->getBufferFormat();
     int bufFmt = buffer->format;
     if (imgReaderFmt != bufFmt) {
-        // Special casing for when producer switches format
-        if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && bufFmt ==
-                HAL_PIXEL_FORMAT_YCrCb_420_SP) {
-            ctx->setBufferFormat(HAL_PIXEL_FORMAT_YCrCb_420_SP);
-            ALOGV("%s: Overriding NV21 to YUV_420_888.", __FUNCTION__);
+        // Special casing for when producer switches to a format compatible with flexible YUV
+        // (HAL_PIXEL_FORMAT_YCbCr_420_888).
+        if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && (bufFmt ==
+                HAL_PIXEL_FORMAT_YCrCb_420_SP || bufFmt == HAL_PIXEL_FORMAT_YV12)) {
+            ctx->setBufferFormat(bufFmt);
+            ALOGV("%s: Overriding buffer format YUV_420_888 to %x.", __FUNCTION__, bufFmt);
         } else {
             // Return the buffer to the queue.
             consumer->unlockBuffer(*buffer);
@@ -847,6 +859,14 @@
 
     // Create byteBuffer from native buffer
     Image_getLockedBufferInfo(env, buffer, idx, &base, &size);
+
+    if (size > static_cast<uint32_t>(INT32_MAX)) {
+        // Byte buffer have 'int capacity', so check the range
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                "Size too large for bytebuffer capacity " PRIu32, size);
+        return NULL;
+    }
+
     byteBuffer = env->NewDirectByteBuffer(base, size);
     // TODO: throw dvm exOutOfMemoryError?
     if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 4fbd2a4..5f27b16 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -110,6 +110,11 @@
     jfieldID certificateData;
 };
 
+struct StateExceptionFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -121,6 +126,7 @@
     IteratorFields iterator;
     EntryFields entry;
     CertificateFields certificate;
+    StateExceptionFields stateException;
     jclass certificateClassId;
     jclass hashmapClassId;
     jclass arraylistClassId;
@@ -212,6 +218,14 @@
     }
 }
 
+static void throwStateException(JNIEnv *env, const char *msg, status_t err) {
+    ALOGE("Illegal state exception: %s (%d)", msg, err);
+
+    jobject exception = env->NewObject(gFields.stateException.classId,
+            gFields.stateException.init, static_cast<int>(err),
+            env->NewStringUTF(msg));
+    env->Throw(static_cast<jthrowable>(exception));
+}
 
 static bool throwExceptionAsNecessary(
         JNIEnv *env, status_t err, const char *msg = NULL) {
@@ -275,8 +289,7 @@
                 msg = errbuf.string();
             }
         }
-        ALOGE("Illegal state exception: %s", msg);
-        jniThrowException(env, "java/lang/IllegalStateException", msg);
+        throwStateException(env, msg, err);
         return true;
     }
     return false;
@@ -608,6 +621,10 @@
 
     FIND_CLASS(clazz, "java/util/ArrayList");
     gFields.arraylistClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+    GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+    gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
 }
 
 static void android_media_MediaDrm_native_setup(
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/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index f092af8..c9d33da 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -165,7 +165,8 @@
   sp<IGraphicBufferProducer> producer;
   sp<IGraphicBufferConsumer> consumer;
   BufferQueue::createBufferQueue(&producer, &consumer);
-  surfaceTexture_ = new GLConsumer(consumer, 0);
+  surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
+          true, false);
   window_ = new Surface(producer);
 
   surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 8a7e642..b6bb578 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -22,7 +22,6 @@
 import android.hardware.ICameraServiceListener;
 import android.hardware.IProCameraCallbacks;
 import android.hardware.IProCameraUser;
-import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.impl.CameraMetadataNative;
@@ -51,8 +50,14 @@
  * </pre>
  */
 public class CameraBinderTest extends AndroidTestCase {
+    private static final int MAX_PARAMETERS_LENGTH = 100;
+
     static String TAG = "CameraBinderTest";
 
+    // From ICameraService.h
+    private static final int API_VERSION_1 = 1;
+    private static final int API_VERSION_2 = 2;
+
     protected CameraBinderTestUtils mUtils;
 
     public CameraBinderTest() {
@@ -95,6 +100,56 @@
         }
     }
 
+    @SmallTest
+    public void testGetLegacyParameters() throws Exception {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+
+            String[] parameters = new String[1];
+            assertEquals("Camera service returned parameters for camera " + cameraId,
+                    CameraBinderTestUtils.NO_ERROR,
+                    mUtils.getCameraService().getLegacyParameters(cameraId, /*out*/parameters));
+            assertNotNull(parameters[0]);
+            assertTrue("Parameters should have at least one character in it",
+                    parameters[0].length() > 0);
+
+            int end = parameters[0].length();
+            if (end > MAX_PARAMETERS_LENGTH) {
+                end = MAX_PARAMETERS_LENGTH;
+            }
+
+            Log.v(TAG, "Camera " + cameraId + " parameters: " + parameters[0].substring(0, end));
+        }
+    }
+
+    /** The camera2 api is only supported on HAL3.2+ devices */
+    @SmallTest
+    public void testSupportsCamera2Api() throws Exception {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+
+            int res = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_2);
+
+            if (res != CameraBinderTestUtils.NO_ERROR && res != CameraBinderTestUtils.EOPNOTSUPP) {
+                fail("Camera service returned bad value when queried if it supports camera2 api: "
+                        + res + " for camera ID " + cameraId);
+            }
+
+            boolean supports = res == CameraBinderTestUtils.NO_ERROR;
+            Log.v(TAG, "Camera " + cameraId + " supports api2: " + supports);
+        }
+    }
+
+    /** The camera1 api is supported on *all* devices regardless of HAL version */
+    @SmallTest
+    public void testSupportsCamera1Api() throws Exception {
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+
+            int res = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_1);
+            assertEquals(
+                    "Camera service returned bad value when queried if it supports camera1 api: "
+                    + res + " for camera ID " + cameraId, CameraBinderTestUtils.NO_ERROR, res);
+        }
+    }
+
     static abstract class DummyBase extends Binder implements android.os.IInterface {
         @Override
         public IBinder asBinder() {
@@ -150,6 +205,37 @@
         }
     }
 
+    @SmallTest
+    public void testConnectLegacy() throws Exception {
+        final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+            ICamera cameraUser = null;
+            ICameraClient dummyCallbacks = new DummyCameraClient();
+
+            String clientPackageName = getContext().getPackageName();
+
+            BinderHolder holder = new BinderHolder();
+
+            try {
+                CameraBinderDecorator.newInstance(mUtils.getCameraService())
+                        .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
+                        clientPackageName,
+                        CameraBinderTestUtils.USE_CALLING_UID, holder);
+                cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+                assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
+
+                Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
+            } catch (RuntimeException e) {
+                // Not all camera device support openLegacy.
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (cameraUser != null) {
+                    cameraUser.disconnect();
+                }
+            }
+        }
+    }
+
     static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
 
         /*
@@ -158,6 +244,7 @@
          * android.hardware.camera2.ICameraDeviceCallbacks#onCameraError(int,
          * android.hardware.camera2.CaptureResultExtras)
          */
+        @Override
         public void onCameraError(int errorCode, CaptureResultExtras resultExtras)
                 throws RemoteException {
             // TODO Auto-generated method stub
@@ -170,6 +257,7 @@
          * android.hardware.camera2.ICameraDeviceCallbacks#onCaptureStarted(
          * android.hardware.camera2.CaptureResultExtras, long)
          */
+        @Override
         public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
                 throws RemoteException {
             // TODO Auto-generated method stub
@@ -183,6 +271,7 @@
          * android.hardware.camera2.impl.CameraMetadataNative,
          * android.hardware.camera2.CaptureResultExtras)
          */
+        @Override
         public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
                 throws RemoteException {
             // TODO Auto-generated method stub
@@ -193,6 +282,7 @@
          * (non-Javadoc)
          * @see android.hardware.camera2.ICameraDeviceCallbacks#onCameraIdle()
          */
+        @Override
         public void onCameraIdle() throws RemoteException {
             // TODO Auto-generated method stub
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
index 1be2a62..6be538a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
@@ -22,6 +22,7 @@
     protected static final int INVALID_OPERATION = -38;
     protected static final int ALREADY_EXISTS = -17;
     public static final int NO_ERROR = 0;
+    public static final int EOPNOTSUPP = -95;
     private final Context mContext;
 
     public CameraBinderTestUtils(Context context) {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
new file mode 100644
index 0000000..14bbe44
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.mediaframeworktest.unit;
+
+import android.hardware.Camera;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * <pre>
+ * adb shell am instrument \
+ *      -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
+ *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ * </pre>
+ */
+public class CameraOpenTest extends junit.framework.TestCase {
+    private static String TAG = "CameraOpenTest";
+
+    private Camera mCamera;
+
+    /**
+     * Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
+     */
+    @SmallTest
+    public void testOpenLegacy() {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            try {
+                mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
+            } catch (RuntimeException e) {
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (mCamera != null) {
+                    mCamera.release();
+                }
+            }
+        }
+    }
+}
diff --git a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
index b769a0c..acb7767 100644
--- a/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
+++ b/media/tests/ScoAudioTest/res/layout/scoaudiotest.xml
@@ -125,6 +125,11 @@
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:text="@string/audiomanagertwo" />
+        <CheckBox
+            android:id="@+id/useVirtualCallCheckBox"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/useVirtualCallCheckText" />
             
     </LinearLayout>
 
diff --git a/media/tests/ScoAudioTest/res/values/strings.xml b/media/tests/ScoAudioTest/res/values/strings.xml
index c3ff6d5..b0284e2 100644
--- a/media/tests/ScoAudioTest/res/values/strings.xml
+++ b/media/tests/ScoAudioTest/res/values/strings.xml
@@ -10,5 +10,5 @@
     <string name="tts_speak">Speak TTS</string>
     <string name="tts_to_file">TTS to file</string>
     <string name="audiomanagertwo">Use different AudioManager for starting SCO</string>
-    
+    <string name="useVirtualCallCheckText">Use Virtual Call</string>
 </resources>
diff --git a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
index 0304640..7e21876 100644
--- a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
+++ b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java
@@ -207,16 +207,25 @@
             if (mForceScoOn != isChecked) {
                 mForceScoOn = isChecked;
                 AudioManager mngr = mAudioManager;
+                boolean useVirtualCall = false;
                 CheckBox box = (CheckBox) findViewById(R.id.useSecondAudioManager);
                 if (box.isChecked()) {
                     Log.i(TAG, "Using 2nd audio manager");
                     mngr = mAudioManager2;
                 }
+                box = (CheckBox) findViewById(R.id.useVirtualCallCheckBox);
+                useVirtualCall = box.isChecked();
 
                 if (mForceScoOn) {
-                    Log.e(TAG, "startBluetoothSco() IN");
-                    mngr.startBluetoothSco();
-                    Log.e(TAG, "startBluetoothSco() OUT");
+                    if (useVirtualCall) {
+                        Log.e(TAG, "startBluetoothScoVirtualCall() IN");
+                        mngr.startBluetoothScoVirtualCall();
+                        Log.e(TAG, "startBluetoothScoVirtualCall() OUT");
+                    } else {
+                        Log.e(TAG, "startBluetoothSco() IN");
+                        mngr.startBluetoothSco();
+                        Log.e(TAG, "startBluetoothSco() OUT");
+                    }
                 } else {
                     Log.e(TAG, "stopBluetoothSco() IN");
                     mngr.stopBluetoothSco();
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
index 3dd988e..229bfdb 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp
@@ -104,7 +104,9 @@
     int height;
     meta->findInt32(kKeyWidth, &width);
     meta->findInt32(kKeyHeight, &height);
-    configBitmapSize(bm, getPrefConfig(k32Bit_SrcDepth, false), width, height);
+    configBitmapSize(
+            bm, getPrefColorType(k32Bit_SrcDepth, false),
+            width, height);
 
     // mode == DecodeBounds
     if (mode == SkImageDecoder::kDecodeBounds_Mode) {
@@ -167,15 +169,9 @@
     return true;
 }
 
-void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref,
+void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkColorType pref,
         int width, int height) {
-    bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType);
-}
-
-SkBitmap::Config OmxJpegImageDecoder::getColorSpaceConfig(
-        SkBitmap::Config pref) {
-
-    // Set the color space to ARGB_8888 for now
+    // Set the color space to ARGB_8888 for now (ignoring pref)
     // because of limitation in hardware support.
-    return SkBitmap::kARGB_8888_Config;
+    bm->setInfo(SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType));
 }
diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
index e431e72..e487245 100644
--- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
+++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.h
@@ -49,9 +49,7 @@
     sp<MediaSource> getDecoder(OMXClient* client, const sp<MediaSource>& source);
     bool decodeSource(sp<MediaSource> decoder, const sp<MediaSource>& source,
             SkBitmap* bm);
-    void configBitmapSize(SkBitmap* bm, SkBitmap::Config pref, int width,
-            int height);
-    SkBitmap::Config getColorSpaceConfig(SkBitmap::Config pref);
+    void configBitmapSize(SkBitmap* bm, SkColorType, int width, int height);
 
     OMXClient mClient;
 };
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index 3f37ed1..b09bc2e 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -171,3 +171,8 @@
 {
     return static_cast<Sensor const*>(sensor)->getStringType().string();
 }
+
+int ASensor_getReportingMode(ASensor const* sensor)
+{
+    return static_cast<Sensor const*>(sensor)->getReportingMode();
+}
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 36c1d5c..52db30a 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.ObbInfo;
 import android.content.res.ObbScanner;
 import android.net.Uri;
@@ -88,7 +89,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 +102,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 +154,13 @@
         /**
          * 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.
          */
+        @Override
         public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
-                long threshold) {
+                long threshold, String abiOverride) {
             PackageInfoLite ret = new PackageInfoLite();
 
             if (packagePath == null) {
@@ -168,14 +169,13 @@
                 return ret;
             }
 
-            DisplayMetrics metrics = new DisplayMetrics();
-            metrics.setToDefaults();
-
-            PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
-            if (pkg == null) {
+            final File apkFile = new File(packagePath);
+            final PackageParser.ApkLite pkg;
+            try {
+                pkg = PackageParser.parseApkLite(apkFile, 0);
+            } catch (PackageParserException e) {
                 Slog.w(TAG, "Failed to parse package");
 
-                final File apkFile = new File(packagePath);
                 if (!apkFile.exists()) {
                     ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
                 } else {
@@ -191,7 +191,7 @@
             ret.verifiers = pkg.verifiers;
 
             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
-                    packagePath, flags, threshold);
+                    packagePath, flags, threshold, abiOverride);
 
             return ret;
         }
@@ -208,11 +208,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 +265,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 +328,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 +344,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 +430,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 +688,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 +757,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 +828,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 +848,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/DocumentsUI/res/values-km-rKH/strings.xml b/packages/DocumentsUI/res/values-km-rKH/strings.xml
index 8c9030d..e8944ec 100644
--- a/packages/DocumentsUI/res/values-km-rKH/strings.xml
+++ b/packages/DocumentsUI/res/values-km-rKH/strings.xml
@@ -20,14 +20,14 @@
     <string name="title_open" msgid="4353228937663917801">"បើក​ពី"</string>
     <string name="title_save" msgid="2433679664882857999">"រក្សា​ទុក​ទៅ"</string>
     <string name="menu_create_dir" msgid="5947289605844398389">"បង្កើត​ថត"</string>
-    <string name="menu_grid" msgid="6878021334497835259">"ទិដ្ឋភាព​ក្រឡា​"</string>
+    <string name="menu_grid" msgid="6878021334497835259">"ទិដ្ឋភាព​ក្រឡា"</string>
     <string name="menu_list" msgid="7279285939892417279">"ទិដ្ឋភាព​បញ្ជី"</string>
     <string name="menu_sort" msgid="7677740407158414452">"តម្រៀប​តាម"</string>
     <string name="menu_search" msgid="3816712084502856974">"ស្វែងរក"</string>
     <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>
@@ -48,7 +48,7 @@
     <string name="pref_advanced_devices" msgid="903257239609301276">"បង្ហាញ​ឧបករណ៍​កម្រិត​ខ្ពស់"</string>
     <string name="pref_file_size" msgid="2826879315743961459">"បង្ហាញ​ទំហំ​ឯកសារ"</string>
     <string name="pref_device_size" msgid="3542106883278997222">"បង្ហាញ​ទំហំ​ឧបករណ៍"</string>
-    <string name="empty" msgid="7858882803708117596">"គ្មានធាតុ​"</string>
+    <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>
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/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index d2bf30c..ab18271 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -71,6 +71,7 @@
     private AppWidgetHost mAppWidgetHost;
     private AppWidgetManager mAppWidgetManager;
     private KeyguardWidgetPager mAppWidgetContainer;
+    // TODO remove transport control references, these don't exist anymore
     private KeyguardTransportControlView mTransportControl;
     private int mAppWidgetToShow;
 
@@ -235,36 +236,6 @@
                 mKeyguardMultiUserSelectorView.finalizeActiveUserView(true);
             }
         }
-        @Override
-        public void onMusicClientIdChanged(
-                int clientGeneration, boolean clearing, android.app.PendingIntent intent) {
-            // Set transport state to invisible until we know music is playing (below)
-            if (DEBUGXPORT && (mClientGeneration != clientGeneration || clearing)) {
-                Log.v(TAG, (clearing ? "hide" : "show") + " transport, gen:" + clientGeneration);
-            }
-            mClientGeneration = clientGeneration;
-            final int newState = (clearing ? TRANSPORT_GONE
-                    : (mTransportState == TRANSPORT_VISIBLE ?
-                    TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE));
-            if (newState != mTransportState) {
-                mTransportState = newState;
-                if (DEBUGXPORT) Log.v(TAG, "update widget: transport state changed");
-                KeyguardHostView.this.post(mSwitchPageRunnable);
-            }
-        }
-        @Override
-        public void onMusicPlaybackStateChanged(int playbackState, long eventTime) {
-            if (DEBUGXPORT) Log.v(TAG, "music state changed: " + playbackState);
-            if (mTransportState != TRANSPORT_GONE) {
-                final int newState = (isMusicPlaying(playbackState) ?
-                        TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE);
-                if (newState != mTransportState) {
-                    mTransportState = newState;
-                    if (DEBUGXPORT) Log.v(TAG, "update widget: play state changed");
-                    KeyguardHostView.this.post(mSwitchPageRunnable);
-                }
-            }
-        }
     };
 
     private static final boolean isMusicPlaying(int playbackState) {
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/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index 7918755..38316ff 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -166,6 +166,11 @@
         return info;
     }
 
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
     // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
     // This is an optimization to ensure we only recompute the patterns when the inputs change.
     private static final class Patterns {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 668e1ef..bf34705 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -91,8 +91,6 @@
     private static final int MSG_KEYGUARD_VISIBILITY_CHANGED = 312;
     protected static final int MSG_BOOT_COMPLETED = 313;
     private static final int MSG_USER_SWITCH_COMPLETE = 314;
-    private static final int MSG_SET_CURRENT_CLIENT_ID = 315;
-    protected static final int MSG_SET_PLAYBACK_STATE = 316;
     protected static final int MSG_USER_INFO_CHANGED = 317;
     protected static final int MSG_REPORT_EMERGENCY_CALL_ACTION = 318;
     private static final int MSG_SCREEN_TURNED_ON = 319;
@@ -184,12 +182,6 @@
                 case MSG_BOOT_COMPLETED:
                     handleBootCompleted();
                     break;
-                case MSG_SET_CURRENT_CLIENT_ID:
-                    handleSetGenerationId(msg.arg1, msg.arg2 != 0, (PendingIntent) msg.obj);
-                    break;
-                case MSG_SET_PLAYBACK_STATE:
-                    handleSetPlaybackState(msg.arg1, msg.arg2, (Long) msg.obj);
-                    break;
                 case MSG_USER_INFO_CHANGED:
                     handleUserInfoChanged(msg.arg1);
                     break;
@@ -206,8 +198,6 @@
         }
     };
 
-    private AudioManager mAudioManager;
-
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
 
     @Override
@@ -257,49 +247,6 @@
 
     private DisplayClientState mDisplayClientState = new DisplayClientState();
 
-    /**
-     * This currently implements the bare minimum required to enable showing and hiding
-     * KeyguardTransportControl.  There's a lot of client state to maintain which is why
-     * KeyguardTransportControl maintains an independent connection while it's showing.
-     */
-    private final IRemoteControlDisplay.Stub mRemoteControlDisplay =
-                new IRemoteControlDisplay.Stub() {
-
-        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
-                long currentPosMs, float speed) {
-            Message msg = mHandler.obtainMessage(MSG_SET_PLAYBACK_STATE,
-                    generationId, state, stateChangeTimeMs);
-            mHandler.sendMessage(msg);
-        }
-
-        public void setMetadata(int generationId, Bundle metadata) {
-
-        }
-
-        public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
-
-        }
-
-        public void setArtwork(int generationId, Bitmap bitmap) {
-
-        }
-
-        public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
-
-        }
-
-        public void setEnabled(boolean enabled) {
-            // no-op: this RemoteControlDisplay is not subject to being disabled.
-        }
-
-        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
-                boolean clearing) throws RemoteException {
-            Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,
-                        clientGeneration, (clearing ? 1 : 0), mediaIntent);
-            mHandler.sendMessage(msg);
-        }
-    };
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
 
         public void onReceive(Context context, Intent intent) {
@@ -501,38 +448,6 @@
         }
     }
 
-    protected void handleSetGenerationId(int clientGeneration, boolean clearing, PendingIntent p) {
-        mDisplayClientState.clientGeneration = clientGeneration;
-        mDisplayClientState.clearing = clearing;
-        mDisplayClientState.intent = p;
-        if (DEBUG)
-            Log.v(TAG, "handleSetGenerationId(g=" + clientGeneration + ", clear=" + clearing + ")");
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onMusicClientIdChanged(clientGeneration, clearing, p);
-            }
-        }
-    }
-
-    protected void handleSetPlaybackState(int generationId, int playbackState, long eventTime) {
-        if (DEBUG)
-            Log.v(TAG, "handleSetPlaybackState(gen=" + generationId
-                + ", state=" + playbackState + ", t=" + eventTime + ")");
-        mDisplayClientState.playbackState = playbackState;
-        mDisplayClientState.playbackEventTime = eventTime;
-        if (generationId == mDisplayClientState.clientGeneration) {
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-                if (cb != null) {
-                    cb.onMusicPlaybackStateChanged(playbackState, eventTime);
-                }
-            }
-        } else {
-            Log.w(TAG, "Ignoring generation id " + generationId + " because it's not current");
-        }
-    }
-
     private void handleUserInfoChanged(int userId) {
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -694,8 +609,6 @@
     protected void handleBootCompleted() {
         if (mBootCompleted) return;
         mBootCompleted = true;
-        mAudioManager = new AudioManager(mContext);
-        mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1013,12 +926,6 @@
         callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
         callback.onClockVisibilityChanged();
         callback.onSimStateChanged(mSimState);
-        callback.onMusicClientIdChanged(
-                mDisplayClientState.clientGeneration,
-                mDisplayClientState.clearing,
-                mDisplayClientState.intent);
-        callback.onMusicPlaybackStateChanged(mDisplayClientState.playbackState,
-                mDisplayClientState.playbackEventTime);
     }
 
     public void sendKeyguardVisibilityChanged(boolean showing) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index bcdf18f..01600d2 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -144,18 +144,6 @@
     public void onBootCompleted() { }
 
     /**
-     * Called when audio client attaches or detaches from AudioManager.
-     */
-    public void onMusicClientIdChanged(int clientGeneration, boolean clearing, PendingIntent intent) { }
-
-    /**
-     * Called when the audio playback state changes.
-     * @param playbackState
-     * @param eventTime
-     */
-    public void onMusicPlaybackStateChanged(int playbackState, long eventTime) { }
-
-    /**
      * Called when the emergency call button is pressed.
      */
     void onEmergencyCallAction() { }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java
index 3e444fa..8945b15 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java
@@ -341,11 +341,11 @@
                         }
                         // Volume buttons should only function for music (local or remote).
                         // TODO: Actually handle MUTE.
-                        mAudioManager.adjustLocalOrRemoteStreamVolume(
-                                AudioManager.STREAM_MUSIC,
+                        mAudioManager.adjustSuggestedStreamVolume(
                                 keyCode == KeyEvent.KEYCODE_VOLUME_UP
                                         ? AudioManager.ADJUST_RAISE
-                                        : AudioManager.ADJUST_LOWER);
+                                        : AudioManager.ADJUST_LOWER /* direction */,
+                                AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
                         // Don't execute default volume behavior
                         return true;
                     } else {
@@ -376,17 +376,13 @@
     }
 
     private void handleMediaKeyEvent(KeyEvent keyEvent) {
-        IAudioService audioService = IAudioService.Stub.asInterface(
-                ServiceManager.checkService(Context.AUDIO_SERVICE));
-        if (audioService != null) {
-            try {
-                audioService.dispatchMediaKeyEvent(keyEvent);
-            } catch (RemoteException e) {
-                Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e);
+        synchronized (this) {
+            if (mAudioManager == null) {
+                mAudioManager = (AudioManager) getContext().getSystemService(
+                        Context.AUDIO_SERVICE);
             }
-        } else {
-            Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event");
         }
+        mAudioManager.dispatchMediaKeyEvent(keyEvent);
     }
 
     @Override
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..2810785 100644
--- a/packages/PrintSpooler/res/values-af/strings.xml
+++ b/packages/PrintSpooler/res/values-af/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Meer opsies"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Afskrifte:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papiergrootte"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papiergrootte:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alles"</item>
     <item msgid="6812869625222503603">"Reikwydte"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Kon nie na lêer skryf nie"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kon nie uitdruktaak genereer nie"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Herprobeer"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Drukker nie beskikbaar nie"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Kanselleer tans …"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-am/strings.xml b/packages/PrintSpooler/res/values-am/strings.xml
index be64c95..dfcc4a1 100644
--- a/packages/PrintSpooler/res/values-am/strings.xml
+++ b/packages/PrintSpooler/res/values-am/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-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..6c52d16 100644
--- a/packages/PrintSpooler/res/values-bg/strings.xml
+++ b/packages/PrintSpooler/res/values-bg/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-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index a429dd5..53190ab 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Més opcions"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Còpies:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Mida del paper"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Mida del paper:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tots"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"No s\'ha pogut escriure al fitxer."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"No s\'ha pogut generar la tasca d\'impressió"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Torna-ho a provar"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Impressora no disponible"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"S\'està cancel·lant…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index 3ddc701..68e8ec3 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/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">"Nastavení tiskárny"</string>
-    <string name="print_button" msgid="645164566271246268">"Tisk"</string>
-    <string name="save_button" msgid="1921310454071758999">"Uložit"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Další možnosti"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Počet kopií:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Velikost papíru"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Velikost papíru:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Vše"</item>
     <item msgid="6812869625222503603">"Rozsah"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Do souboru nelze zapisovat."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskovou úlohu nelze vytvořit"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Opakovat"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Tiskárna je nedostupná."</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Rušení…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-da/strings.xml b/packages/PrintSpooler/res/values-da/strings.xml
index 1a871f8..368c3768 100644
--- a/packages/PrintSpooler/res/values-da/strings.xml
+++ b/packages/PrintSpooler/res/values-da/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">"Printerindstillinger"</string>
-    <string name="print_button" msgid="645164566271246268">"Udskriv"</string>
-    <string name="save_button" msgid="1921310454071758999">"Gem"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Flere valgmuligheder"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopier:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papirstørrelse"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papirstørrelse:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Der kunne ikke skrives til filen"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Der kunne ikke genereres et udskriftsjob"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Prøv igen"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printeren er utilgængelig"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Annullerer…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-de/strings.xml b/packages/PrintSpooler/res/values-de/strings.xml
index 6b83ac3..8dc52a2 100644
--- a/packages/PrintSpooler/res/values-de/strings.xml
+++ b/packages/PrintSpooler/res/values-de/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Weitere Optionen"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopien:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papierformat"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papierformat:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Bereich"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Fehler beim Schreiben in Datei"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Druckauftrag konnte nicht generiert werden."</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Erneut versuchen"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Drucker nicht verfügbar"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Vorgang wird abgebrochen..."</string>
 </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..90e81bd 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Más opciones"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copias:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Tamaño del papel"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Tamaño de papel:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"No se pudo escribir en el archivo."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Reintentar"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Impresora no disponible"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Cancelando…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 0225cad..101831a 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Más opciones"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copias:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Tamaño del papel"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Tamaño del papel:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Todo"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Error al escribir en el archivo"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Reintentar"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Impresora no disponible"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Cancelando…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-et-rEE/strings.xml b/packages/PrintSpooler/res/values-et-rEE/strings.xml
index 2b3b352..24e1e54 100644
--- a/packages/PrintSpooler/res/values-et-rEE/strings.xml
+++ b/packages/PrintSpooler/res/values-et-rEE/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Rohkem valikuid"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Koopiad:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Paberi suurus"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Paberi suurus:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Kõik"</item>
     <item msgid="6812869625222503603">"Vahemik"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Failile ei saanud kirjutada."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Prinditööd ei saanud luua"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Proovi uuesti"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printer pole saadaval"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Tühistamine ..."</string>
 </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..a92b860 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Lisäasetukset"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopiot:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Paperikoko"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Paperikoko:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Kaikki"</item>
     <item msgid="6812869625222503603">"Väli"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Tiedostoon kirjoittaminen epäonnistui"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tulostustyötä ei voitu luoda"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Yritä uudelleen"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Tulostin ei ole käytettävissä"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Peruutetaan…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index 9a3352c..b7c0247 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Plus d\'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">"Format du papier"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copies :"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Taille du papier"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Taille du papier :"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tous"</item>
     <item msgid="6812869625222503603">"Plage"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Impossible d\'écrire dans le fichier"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Réessayer"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Imprimante indisponible"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Annulation en cours..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 17fabdc..3150269 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Plus d\'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">"Format du papier"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copies :"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Taille du papier"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Taille du papier :"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tout"</item>
     <item msgid="6812869625222503603">"Plage"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Impossible d\'écrire dans le fichier."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression."</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Réessayer"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Imprimante indisponible."</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Annulation en cours…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 60406b7..ad03d7f 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/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-hr/strings.xml b/packages/PrintSpooler/res/values-hr/strings.xml
index 7182cf6..215c46b 100644
--- a/packages/PrintSpooler/res/values-hr/strings.xml
+++ b/packages/PrintSpooler/res/values-hr/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">"Postavke pisača"</string>
-    <string name="print_button" msgid="645164566271246268">"Ispis"</string>
-    <string name="save_button" msgid="1921310454071758999">"Spremi"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Više opcija"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopije:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Veličina papira"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Veličina papira:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Sve"</item>
     <item msgid="6812869625222503603">"Raspon"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Zapisivanje u datoteku nije uspjelo"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Zadatak ispisa nije generiran"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Pokušajte ponovno"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Pisač nije dostupan"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Otkazivanje…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-hu/strings.xml b/packages/PrintSpooler/res/values-hu/strings.xml
index 6a0741b..314025d 100644
--- a/packages/PrintSpooler/res/values-hu/strings.xml
+++ b/packages/PrintSpooler/res/values-hu/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"További lehetőségek"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Példányszám:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papírméret"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papírméret:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Összes"</item>
     <item msgid="6812869625222503603">"Tartomány"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Nem sikerült írni a fájlba"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nem sikerült létrehozni a nyomtatási feladatot."</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Újra"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"A nyomtató nem érhető el"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Megszakítás..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-hy-rAM/strings.xml b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
index 1423b82..de5c2bfb 100644
--- a/packages/PrintSpooler/res/values-hy-rAM/strings.xml
+++ b/packages/PrintSpooler/res/values-hy-rAM/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-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index 2b80d07..77180c4 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/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">"Setelan printer"</string>
-    <string name="print_button" msgid="645164566271246268">"Cetak"</string>
-    <string name="save_button" msgid="1921310454071758999">"Simpan"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Opsi lainnya"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Salinan:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Ukuran kertas"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Ukuran kertas:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Semua"</item>
     <item msgid="6812869625222503603">"Rentang"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Tidak dapat menulis ke file"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat membuat tugas cetak"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Coba lagi"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printer tidak tersedia"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Membatalkan…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index 96c3b48..f0ef878 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/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">"Impostazioni stampante"</string>
-    <string name="print_button" msgid="645164566271246268">"Stampa"</string>
-    <string name="save_button" msgid="1921310454071758999">"Salva"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Altre opzioni"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copie:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Dimensioni carta"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Dimensioni carta:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tutte"</item>
     <item msgid="6812869625222503603">"Intervallo"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Impossibile scrivere sul file"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossibile generare processo di stampa"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Riprova"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Stampante non disponibile"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Annullamento in corso…"</string>
 </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..4cc6466 100644
--- a/packages/PrintSpooler/res/values-ja/strings.xml
+++ b/packages/PrintSpooler/res/values-ja/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-ka-rGE/strings.xml b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
index d36b7c9..87a1711 100644
--- a/packages/PrintSpooler/res/values-ka-rGE/strings.xml
+++ b/packages/PrintSpooler/res/values-ka-rGE/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">"ბეჭდვის 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-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index ba3c042..0146ab7 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/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>
@@ -60,10 +60,9 @@
   </plurals>
     <string name="cancel" msgid="4373674107267141885">"បោះបង់"</string>
     <string name="restart" msgid="2472034227037808749">"ចាប់ផ្ដើម​ឡើងវិញ"</string>
-    <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មាន​​​ការ​ភ្ជាប់​ទៅ​ម៉ាស៊ីន​បោះពុម្ព​"</string>
+    <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 +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-ko/strings.xml b/packages/PrintSpooler/res/values-ko/strings.xml
index 3b2fef7..65df4fb 100644
--- a/packages/PrintSpooler/res/values-ko/strings.xml
+++ b/packages/PrintSpooler/res/values-ko/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">"PDF 파일을 생성하지 못했습니다."</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-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..d06660c 100644
--- a/packages/PrintSpooler/res/values-lo-rLA/strings.xml
+++ b/packages/PrintSpooler/res/values-lo-rLA/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-lt/strings.xml b/packages/PrintSpooler/res/values-lt/strings.xml
index 0c4e386..a42bdd9 100644
--- a/packages/PrintSpooler/res/values-lt/strings.xml
+++ b/packages/PrintSpooler/res/values-lt/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">"Spausdintuvo nustatymai"</string>
-    <string name="print_button" msgid="645164566271246268">"Spausdinti"</string>
-    <string name="save_button" msgid="1921310454071758999">"Išsaugoti"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Daugiau parinkčių"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopijų:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Popieriaus dydis"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Popieriaus dydis:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Visi"</item>
     <item msgid="6812869625222503603">"Diapazonas"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Nepavyko įrašyti į failą"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nepavyko sukurti spausdinimo užduoties"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Bandykite dar kartą"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Spausdintuvas nepasiekiamas"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Atšaukiama…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-lv/strings.xml b/packages/PrintSpooler/res/values-lv/strings.xml
index 3fffdfe..3afba54 100644
--- a/packages/PrintSpooler/res/values-lv/strings.xml
+++ b/packages/PrintSpooler/res/values-lv/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">"Printera iestatījumi"</string>
-    <string name="print_button" msgid="645164566271246268">"Drukāt"</string>
-    <string name="save_button" msgid="1921310454071758999">"Saglabāt"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Citas opcijas"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Eksemplāri:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papīra izmērs"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papīra izmērs:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Visi"</item>
     <item msgid="6812869625222503603">"Diapazons"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Nevarēja ierakstīt failā."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nevarēja ģenerēt drukas darbu"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Mēģināt vēlreiz"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printeris nav pieejams."</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Notiek atcelšana…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-mn-rMN/strings.xml b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
index e429387..ffe98fe 100644
--- a/packages/PrintSpooler/res/values-mn-rMN/strings.xml
+++ b/packages/PrintSpooler/res/values-mn-rMN/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">"Хар &amp; Цагаан"</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-ms-rMY/strings.xml b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
index cca5d2c..7141aeb 100644
--- a/packages/PrintSpooler/res/values-ms-rMY/strings.xml
+++ b/packages/PrintSpooler/res/values-ms-rMY/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Lagi pilihan"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Salinan:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Saiz kertas"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Saiz kertas:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Semua"</item>
     <item msgid="6812869625222503603">"Julat"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Tidak dapat menulis ke fail"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat menjana kerja cetakan"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Cuba semula"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Pencetak tidak tersedia"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Membatalkan..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-nb/strings.xml b/packages/PrintSpooler/res/values-nb/strings.xml
index f6a60c6..8c66bfb 100644
--- a/packages/PrintSpooler/res/values-nb/strings.xml
+++ b/packages/PrintSpooler/res/values-nb/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Flere alternativer"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopier:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papirstørrelse"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papirstørrelse:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Område"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Kunne ikke skrive til filen"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kunne ikke generere utskriftsjobben"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Prøv på nytt"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Skriveren er utilgjengelig"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Avbryter …"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-nl/strings.xml b/packages/PrintSpooler/res/values-nl/strings.xml
index dc12508..caa5c80 100644
--- a/packages/PrintSpooler/res/values-nl/strings.xml
+++ b/packages/PrintSpooler/res/values-nl/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Meer opties"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Aantal:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Papierformaat"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Papierformaat:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Bereik"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Kan niet naar bestand schrijven"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kan de afdruktaak niet genereren"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Opnieuw proberen"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printer niet beschikbaar"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Annuleren…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index 81acc76..d19f2ac 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Więcej opcji"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopie:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Rozmiar papieru"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Rozmiar papieru:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Wszystkie"</item>
     <item msgid="6812869625222503603">"Zakres"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Nie można zapisać pliku"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nie udało się wygenerować zadania drukowania"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Ponów próbę"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Drukarka niedostępna"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Anuluję…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index 6bfc395..6970730 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/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">"Definições da impressora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimir"</string>
-    <string name="save_button" msgid="1921310454071758999">"Guardar"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Mais opções"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Cópias:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Tamanho do papel"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Tamanho do papel:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Não foi possível gravar no ficheiro"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar a tarefa de impressão"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Tentar novamente"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Impressora não disponível"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"A cancelar…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index 82c157d..8b2a6fb 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Mais opções"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Cópias:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Tamanho do papel"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Tamanho do papel:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Não foi possível gravar no arquivo"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar o trabalho de impressão"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Tentar novamente"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Impressora indisponível"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Cancelando..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index 79c3bdc..6706cf6 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Mai multe opțiuni"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copii:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Formatul hârtiei"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Formatul hârtiei:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Toate"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Nu s-a putut scrie în fișier."</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nu s-a putut genera sarcina de printare"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Reîncercați"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Imprimanta nu este disponibilă"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"În curs de anulare..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-ru/strings.xml b/packages/PrintSpooler/res/values-ru/strings.xml
index a27f3c8..2256be4 100644
--- a/packages/PrintSpooler/res/values-ru/strings.xml
+++ b/packages/PrintSpooler/res/values-ru/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-sk/strings.xml b/packages/PrintSpooler/res/values-sk/strings.xml
index 39a022d..a4f0444 100644
--- a/packages/PrintSpooler/res/values-sk/strings.xml
+++ b/packages/PrintSpooler/res/values-sk/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Ďalšie možnosti"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Počet kópií:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Veľkosť papiera"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Veľkosť papiera:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Všetky"</item>
     <item msgid="6812869625222503603">"Rozsah"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Do súboru nie je možné zapisovať"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tlačovú úlohu nie je možné vytvoriť"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Opakovať"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Tlačiareň nie je k dispozícii"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Ruší sa..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-sl/strings.xml b/packages/PrintSpooler/res/values-sl/strings.xml
index e299508..672af526 100644
--- a/packages/PrintSpooler/res/values-sl/strings.xml
+++ b/packages/PrintSpooler/res/values-sl/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Več možnosti"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Št. kopij:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Velikost papirja"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Velikost papirja:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Vse"</item>
     <item msgid="6812869625222503603">"Obseg"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"V datoteko ni bilo mogoče zapisovati"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskalnega opravila ni bilo mogoče ustvariti"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Poskusi znova"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Tiskalnik ni na voljo"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Preklic …"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index cbab1a6..ad45a93 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/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-sv/strings.xml b/packages/PrintSpooler/res/values-sv/strings.xml
index 2286fce..a777165 100644
--- a/packages/PrintSpooler/res/values-sv/strings.xml
+++ b/packages/PrintSpooler/res/values-sv/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Fler alternativ"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopior:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Pappersstorlek"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Pappersstorlek:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Alla"</item>
     <item msgid="6812869625222503603">"Intervall"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Det gick inte att skriva till filen"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Det gick inte att skapa utskriftsjobbet"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Försök igen"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Skrivaren är inte tillgänglig"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Avbryter ..."</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-sw/strings.xml b/packages/PrintSpooler/res/values-sw/strings.xml
index a84e9b3..4c7b3c3 100644
--- a/packages/PrintSpooler/res/values-sw/strings.xml
+++ b/packages/PrintSpooler/res/values-sw/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Chaguo zaidi"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Nakala:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Ukubwa wa karatasi"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Ukubwa wa karatasi:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Zote"</item>
     <item msgid="6812869625222503603">"Masafa"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Haikuweza kuandika kwenye faili"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Haikuweza kuleta kazi ya kuchapisha"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Jaribu tena"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printa haipatikani"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Inaghairi..."</string>
 </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..e3e09d3 100644
--- a/packages/PrintSpooler/res/values-th/strings.xml
+++ b/packages/PrintSpooler/res/values-th/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-tl/strings.xml b/packages/PrintSpooler/res/values-tl/strings.xml
index a72c937..bb7d0bf 100644
--- a/packages/PrintSpooler/res/values-tl/strings.xml
+++ b/packages/PrintSpooler/res/values-tl/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">"Mga setting ng printer"</string>
-    <string name="print_button" msgid="645164566271246268">"I-print"</string>
-    <string name="save_button" msgid="1921310454071758999">"I-save"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Higit pang mga opsyon"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Dami ng kopya:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Laki ng papel"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Laki ng papel:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Lahat"</item>
     <item msgid="6812869625222503603">"Sakop"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Hindi makapag-write sa file"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Hindi mabuo ang pag-print"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Subukang muli"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Hindi available ang printer"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Kinakansela…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-tr/strings.xml b/packages/PrintSpooler/res/values-tr/strings.xml
index c6e302d..5f81654 100644
--- a/packages/PrintSpooler/res/values-tr/strings.xml
+++ b/packages/PrintSpooler/res/values-tr/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">"Yazıcı ayarları"</string>
-    <string name="print_button" msgid="645164566271246268">"Yazdır"</string>
-    <string name="save_button" msgid="1921310454071758999">"Kaydet"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Diğer seçenekler"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Kopya sayısı"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Kağıt boyutu"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Kağıt boyutu:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tümü"</item>
     <item msgid="6812869625222503603">"Aralık"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Dosyaya yazılamadı"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Yazdırma işi oluşturulamadı"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Yeniden dene"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Yazıcı kullanılamıyor"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"İptal ediliyor…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-uk/strings.xml b/packages/PrintSpooler/res/values-uk/strings.xml
index 4f526d0..81be680 100644
--- a/packages/PrintSpooler/res/values-uk/strings.xml
+++ b/packages/PrintSpooler/res/values-uk/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-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index d21172c..84e97fd 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/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">"Cài đặt máy in"</string>
-    <string name="print_button" msgid="645164566271246268">"In"</string>
-    <string name="save_button" msgid="1921310454071758999">"Lưu"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Thêm tùy chọn"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Bản sao:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Khổ giấy"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Khổ giấy:"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Tất cả"</item>
     <item msgid="6812869625222503603">"Dãy"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Không thể ghi vào tệp"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Không thể tạo lệnh in"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Thử lại"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Máy in không khả dụng"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Đang hủy…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index e43ea60..a904388 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/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-zh-rHK/strings.xml b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
index 2c2422a9..534d4bf 100644
--- a/packages/PrintSpooler/res/values-zh-rHK/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rHK/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-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 0fe88d9..95960c4 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/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-zu/strings.xml b/packages/PrintSpooler/res/values-zu/strings.xml
index 08e31e1..f46d2db9a 100644
--- a/packages/PrintSpooler/res/values-zu/strings.xml
+++ b/packages/PrintSpooler/res/values-zu/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">"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>
+    <string name="more_options_button" msgid="2243228396432556771">"Izinketho eziningi"</string>
     <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>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Amakhophi:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Usayizi wekhasi"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Usayizi wekhasi"</string>
     <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 +63,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 +75,9 @@
     <item msgid="7421377442011699994">"Konke"</item>
     <item msgid="6812869625222503603">"Ibanga"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Ayikwazanga ukubhala kufayela"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Ayikwazanga ukukhiqiza umsebenzi wokuphrinta"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Zama futhi"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Iphrinta ayitholakali"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Iyakhansela…"</string>
 </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/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
deleted file mode 100644
index 9831839..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ /dev/null
@@ -1,708 +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.ComponentName;
-import android.content.Context;
-import android.content.Loader;
-import android.content.pm.ServiceInfo;
-import android.os.AsyncTask;
-import android.print.PrintManager;
-import android.print.PrinterDiscoverySession;
-import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.printservice.PrintServiceInfo;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import libcore.io.IoUtils;
-
-/**
- * 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>> {
-    private static final String LOG_TAG = "FusedPrintersProvider";
-
-    private static final boolean DEBUG = false;
-
-    private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
-    private static final int MAX_HISTORY_LENGTH = 50;
-
-    private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
-
-    private final List<PrinterInfo> mPrinters =
-            new ArrayList<PrinterInfo>();
-
-    private final List<PrinterInfo> mFavoritePrinters =
-            new ArrayList<PrinterInfo>();
-
-    private final PersistenceManager mPersistenceManager;
-
-    private PrinterDiscoverySession mDiscoverySession;
-
-    private PrinterId mTrackedPrinter;
-
-    private boolean mPrintersUpdatedBefore;
-
-    public FusedPrintersProvider(Context context) {
-        super(context);
-        mPersistenceManager = new PersistenceManager(context);
-    }
-
-    public void addHistoricalPrinter(PrinterInfo printer) {
-        mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
-    }
-
-    private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
-            ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
-        List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
-
-        // Add the updated favorite printers.
-        final int favoritePrinterCount = favoritePrinters.size();
-        for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = favoritePrinters.valueAt(i);
-            PrinterInfo updatedPrinter = discoveredPrinters.remove(
-                    favoritePrinter.getId());
-            if (updatedPrinter != null) {
-                printers.add(updatedPrinter);
-            } else {
-                printers.add(favoritePrinter);
-            }
-        }
-
-        // Add other updated printers.
-        final int printerCount = mPrinters.size();
-        for (int i = 0; i < printerCount; i++) {
-            PrinterInfo printer = mPrinters.get(i);
-            PrinterInfo updatedPrinter = discoveredPrinters.remove(
-                    printer.getId());
-            if (updatedPrinter != null) {
-                printers.add(updatedPrinter);
-            }
-        }
-
-        // Add the new printers, i.e. what is left.
-        printers.addAll(discoveredPrinters.values());
-
-        // Update the list of printers.
-        mPrinters.clear();
-        mPrinters.addAll(printers);
-
-        if (isStarted()) {
-            // If stated deliver the new printers.
-            deliverResult(printers);
-        } else {
-            // Otherwise, take a note for the change.
-            onContentChanged();
-        }
-    }
-
-    @Override
-    protected void onStartLoading() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onStartLoading() " + FusedPrintersProvider.this.hashCode());
-        }
-        // The contract is that if we already have a valid,
-        // result the we have to deliver it immediately.
-        if (!mPrinters.isEmpty()) {
-            deliverResult(new ArrayList<PrinterInfo>(mPrinters));
-        }
-        // Always load the data to ensure discovery period is
-        // started and to make sure obsolete printers are updated.
-        onForceLoad();
-    }
-
-    @Override
-    protected void onStopLoading() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode());
-        }
-        onCancelLoad();
-    }
-
-    @Override
-    protected void onForceLoad() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onForceLoad() " + FusedPrintersProvider.this.hashCode());
-        }
-        loadInternal();
-    }
-
-    private void loadInternal() {
-        if (mDiscoverySession == null) {
-            PrintManager printManager = (PrintManager) getContext()
-                    .getSystemService(Context.PRINT_SERVICE);
-            mDiscoverySession = printManager.createPrinterDiscoverySession();
-            mPersistenceManager.readPrinterHistory();
-        } else if (mPersistenceManager.isHistoryChanged()) {
-            mPersistenceManager.readPrinterHistory();
-        }
-        if (mPersistenceManager.isReadHistoryCompleted()
-                && !mDiscoverySession.isPrinterDiscoveryStarted()) {
-            mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
-                @Override
-                public void onPrintersChanged() {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "onPrintersChanged() count:"
-                                + mDiscoverySession.getPrinters().size()
-                                + " " + FusedPrintersProvider.this.hashCode());
-                    }
-                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
-                }
-            });
-            final int favoriteCount = mFavoritePrinters.size();
-            List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
-            for (int i = 0; i < favoriteCount; i++) {
-                printerIds.add(mFavoritePrinters.get(i).getId());
-            }
-            mDiscoverySession.startPrinterDisovery(printerIds);
-            List<PrinterInfo> printers = mDiscoverySession.getPrinters();
-            if (!printers.isEmpty()) {
-                updatePrinters(printers, mFavoritePrinters);
-            }
-        }
-    }
-
-    private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {
-        if (mPrintersUpdatedBefore && mPrinters.equals(printers)
-                && mFavoritePrinters.equals(favoritePrinters)) {
-            return;
-        }
-
-        mPrintersUpdatedBefore = true;
-
-        ArrayMap<PrinterId, PrinterInfo> printersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
-        final int printerCount = printers.size();
-        for (int i = 0; i < printerCount; i++) {
-            PrinterInfo printer = printers.get(i);
-            printersMap.put(printer.getId(), printer);
-        }
-
-        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
-                new ArrayMap<PrinterId, PrinterInfo>();
-        final int favoritePrinterCount = favoritePrinters.size();
-        for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = favoritePrinters.get(i);
-            favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter);
-        }
-
-        computeAndDeliverResult(printersMap, favoritePrintersMap);
-    }
-
-    @Override
-    protected boolean onCancelLoad() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onCancelLoad() " + FusedPrintersProvider.this.hashCode());
-        }
-        return cancelInternal();
-    }
-
-    private boolean cancelInternal() {
-        if (mDiscoverySession != null
-                && mDiscoverySession.isPrinterDiscoveryStarted()) {
-            if (mTrackedPrinter != null) {
-                mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
-                mTrackedPrinter = null;
-            }
-            mDiscoverySession.stopPrinterDiscovery();
-            return true;
-        } else if (mPersistenceManager.isReadHistoryInProgress()) {
-            return mPersistenceManager.stopReadPrinterHistory();
-        }
-        return false;
-    }
-
-    @Override
-    protected void onReset() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onReset() " + FusedPrintersProvider.this.hashCode());
-        }
-        onStopLoading();
-        mPrinters.clear();
-        if (mDiscoverySession != null) {
-            mDiscoverySession.destroy();
-            mDiscoverySession = null;
-        }
-    }
-
-    @Override
-    protected void onAbandon() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onAbandon() " + FusedPrintersProvider.this.hashCode());
-        }
-        onStopLoading();
-    }
-
-    public void setTrackedPrinter(PrinterId printerId) {
-        if (isStarted() && mDiscoverySession != null
-                && mDiscoverySession.isPrinterDiscoveryStarted()) {
-            if (mTrackedPrinter != null) {
-                if (mTrackedPrinter.equals(printerId)) {
-                    return;
-                }
-                mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
-            }
-            mTrackedPrinter = printerId;
-            mDiscoverySession.startPrinterStateTracking(printerId);
-        }
-    }
-
-    public boolean isFavoritePrinter(PrinterId printerId) {
-        final int printerCount = mFavoritePrinters.size();
-        for (int i = 0; i < printerCount; i++) {
-            PrinterInfo favoritePritner = mFavoritePrinters.get(i);
-            if (favoritePritner.getId().equals(printerId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void forgetFavoritePrinter(PrinterId printerId) {
-        List<PrinterInfo> newFavoritePrinters = null;
-
-        // Remove the printer from the favorites.
-        final int favoritePrinterCount = mFavoritePrinters.size();
-        for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
-            if (favoritePrinter.getId().equals(printerId)) {
-                newFavoritePrinters = new ArrayList<PrinterInfo>();
-                newFavoritePrinters.addAll(mPrinters);
-                newFavoritePrinters.remove(i);
-                break;
-            }
-        }
-
-        // If we removed a favorite printer, we have work to do.
-        if (newFavoritePrinters != null) {
-            // Remove the printer from history and persist the latter.
-            mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
-
-            // Recompute and deliver the printers.
-            updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
-        }
-    }
-
-    private final class PersistenceManager {
-        private static final String PERSIST_FILE_NAME = "printer_history.xml";
-
-        private static final String TAG_PRINTERS = "printers";
-
-        private static final String TAG_PRINTER = "printer";
-        private static final String TAG_PRINTER_ID = "printerId";
-
-        private static final String ATTR_LOCAL_ID = "localId";
-        private static final String ATTR_SERVICE_NAME = "serviceName";
-
-        private static final String ATTR_NAME = "name";
-        private static final String ATTR_DESCRIPTION = "description";
-        private static final String ATTR_STATUS = "status";
-
-        private final AtomicFile mStatePersistFile;
-
-        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
-
-        private boolean mReadHistoryCompleted;
-        private boolean mReadHistoryInProgress;
-
-        private ReadTask mReadTask;
-
-        private volatile long mLastReadHistoryTimestamp;
-
-        private PersistenceManager(Context context) {
-            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
-                    PERSIST_FILE_NAME));
-        }
-
-        public boolean isReadHistoryInProgress() {
-            return mReadHistoryInProgress;
-        }
-
-        public boolean isReadHistoryCompleted() {
-            return mReadHistoryCompleted;
-        }
-
-        public boolean stopReadPrinterHistory() {
-            final boolean cancelled = mReadTask.cancel(true);
-            mReadTask = null;
-            return cancelled;
-        }
-
-        public void readPrinterHistory() {
-            if (DEBUG) {
-                Log.i(LOG_TAG, "read history started "
-                        + FusedPrintersProvider.this.hashCode());
-            }
-            mReadHistoryInProgress = true;
-            mReadTask = new ReadTask();
-            mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-        }
-
-        @SuppressWarnings("unchecked")
-        public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
-            if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
-                mHistoricalPrinters.remove(0);
-            }
-            mHistoricalPrinters.add(printer);
-            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                    new ArrayList<PrinterInfo>(mHistoricalPrinters));
-        }
-
-        @SuppressWarnings("unchecked")
-        public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
-            boolean writeHistory = false;
-            final int printerCount = mHistoricalPrinters.size();
-            for (int i = printerCount - 1; i >= 0; i--) {
-                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
-                if (historicalPrinter.getId().equals(printerId)) {
-                    mHistoricalPrinters.remove(i);
-                    writeHistory = true;
-                }
-            }
-            if (writeHistory) {
-                new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                        new ArrayList<PrinterInfo>(mHistoricalPrinters));
-            }
-        }
-
-        public boolean isHistoryChanged() {
-            return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
-        }
-
-        private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
-            Map<PrinterId, PrinterRecord> recordMap =
-                    new ArrayMap<PrinterId, PrinterRecord>();
-
-            // Recompute the weights.
-            float currentWeight = 1.0f;
-            final int printerCount = printers.size();
-            for (int i = printerCount - 1; i >= 0; i--) {
-                PrinterInfo printer = printers.get(i);
-                // Aggregate weight for the same printer
-                PrinterRecord record = recordMap.get(printer.getId());
-                if (record == null) {
-                    record = new PrinterRecord(printer);
-                    recordMap.put(printer.getId(), record);
-                }
-                record.weight += currentWeight;
-                currentWeight *= WEIGHT_DECAY_COEFFICIENT;
-            }
-
-            // Soft the favorite printers.
-            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
-                    recordMap.values());
-            Collections.sort(favoriteRecords);
-
-            // Write the favorites to the output.
-            final int favoriteCount = Math.min(favoriteRecords.size(),
-                    MAX_FAVORITE_PRINTER_COUNT);
-            List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
-            for (int i = 0; i < favoriteCount; i++) {
-                PrinterInfo printer = favoriteRecords.get(i).printer;
-                favoritePrinters.add(printer);
-            }
-
-            return favoritePrinters;
-        }
-
-        private final class PrinterRecord implements Comparable<PrinterRecord> {
-            public final PrinterInfo printer;
-            public float weight;
-
-            public PrinterRecord(PrinterInfo printer) {
-                this.printer = printer;
-            }
-
-            @Override
-            public int compareTo(PrinterRecord another) {
-                return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
-            }
-        }
-
-        private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {
-            @Override
-            protected List<PrinterInfo> doInBackground(Void... args) {
-               return doReadPrinterHistory();
-            }
-
-            @Override
-            protected void onPostExecute(List<PrinterInfo> printers) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "read history completed "
-                            + FusedPrintersProvider.this.hashCode());
-                }
-
-                // Ignore printer records whose target services are not enabled.
-                PrintManager printManager = (PrintManager) getContext()
-                        .getSystemService(Context.PRINT_SERVICE);
-                List<PrintServiceInfo> services = printManager
-                        .getEnabledPrintServices();
-
-                Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();
-                final int installedServiceCount = services.size();
-                for (int i = 0; i < installedServiceCount; i++) {
-                    ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
-                    ComponentName componentName = new ComponentName(
-                            serviceInfo.packageName, serviceInfo.name);
-                    enabledComponents.add(componentName);
-                }
-
-                final int printerCount = printers.size();
-                for (int i = printerCount - 1; i >= 0; i--) {
-                    ComponentName printerServiceName = printers.get(i).getId().getServiceName();
-                    if (!enabledComponents.contains(printerServiceName)) {
-                        printers.remove(i);
-                    }
-                }
-
-                // Store the filtered list.
-                mHistoricalPrinters = printers;
-
-                // Compute the favorite printers.
-                mFavoritePrinters.clear();
-                mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters));
-
-                mReadHistoryInProgress = false;
-                mReadHistoryCompleted = true;
-
-                // Deliver the printers.
-                updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters);
-
-                // Loading the available printers if needed.
-                loadInternal();
-
-                // We are done.
-                mReadTask = null;
-            }
-
-            private List<PrinterInfo> doReadPrinterHistory() {
-                FileInputStream in = null;
-                try {
-                    in = mStatePersistFile.openRead();
-                } catch (FileNotFoundException fnfe) {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "No existing printer history "
-                                + FusedPrintersProvider.this.hashCode());
-                    }
-                    return new ArrayList<PrinterInfo>();
-                }
-                try {
-                    List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
-                    XmlPullParser parser = Xml.newPullParser();
-                    parser.setInput(in, null);
-                    parseState(parser, printers);
-                    // Take a note which version of the history was read.
-                    mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
-                    return printers;
-                } catch (IllegalStateException ise) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ise);
-                } catch (NullPointerException npe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", npe);
-                } catch (NumberFormatException nfe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", nfe);
-                } catch (XmlPullParserException xppe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", xppe);
-                } catch (IOException ioe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", ioe);
-                } catch (IndexOutOfBoundsException iobe) {
-                    Slog.w(LOG_TAG, "Failed parsing ", iobe);
-                } finally {
-                    IoUtils.closeQuietly(in);
-                }
-
-                return Collections.emptyList();
-            }
-
-            private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
-                    throws IOException, XmlPullParserException {
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
-                parser.next();
-
-                while (parsePrinter(parser, outPrinters)) {
-                    // Be nice and respond to cancellation
-                    if (isCancelled()) {
-                        return;
-                    }
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
-            }
-
-            private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
-                    throws IOException, XmlPullParserException {
-                skipEmptyTextTags(parser);
-                if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
-                    return false;
-                }
-
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
-                final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
-
-                parser.next();
-
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
-                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
-                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
-                        null, ATTR_SERVICE_NAME));
-                PrinterId printerId =  new PrinterId(service, localId);
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
-                parser.next();
-
-                PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
-                builder.setDescription(description);
-                PrinterInfo printer = builder.build();
-
-                outPrinters.add(printer);
-
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "[RESTORED] " + printer);
-                }
-
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
-
-                return true;
-            }
-
-            private void expect(XmlPullParser parser, int type, String tag)
-                    throws IOException, XmlPullParserException {
-                if (!accept(parser, type, tag)) {
-                    throw new XmlPullParserException("Exepected event: " + type
-                            + " and tag: " + tag + " but got event: " + parser.getEventType()
-                            + " and tag:" + parser.getName());
-                }
-            }
-
-            private void skipEmptyTextTags(XmlPullParser parser)
-                    throws IOException, XmlPullParserException {
-                while (accept(parser, XmlPullParser.TEXT, null)
-                        && "\n".equals(parser.getText())) {
-                    parser.next();
-                }
-            }
-
-            private boolean accept(XmlPullParser parser, int type, String tag)
-                    throws IOException, XmlPullParserException {
-                if (parser.getEventType() != type) {
-                    return false;
-                }
-                if (tag != null) {
-                    if (!tag.equals(parser.getName())) {
-                        return false;
-                    }
-                } else if (parser.getName() != null) {
-                    return false;
-                }
-                return true;
-            }
-        };
-
-        private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
-            @Override
-            protected Void doInBackground(List<PrinterInfo>... printers) {
-                doWritePrinterHistory(printers[0]);
-                return null;
-            }
-
-            private void doWritePrinterHistory(List<PrinterInfo> printers) {
-                FileOutputStream out = null;
-                try {
-                    out = mStatePersistFile.startWrite();
-
-                    XmlSerializer serializer = new FastXmlSerializer();
-                    serializer.setOutput(out, "utf-8");
-                    serializer.startDocument(null, true);
-                    serializer.startTag(null, TAG_PRINTERS);
-
-                    final int printerCount = printers.size();
-                    for (int i = 0; i < printerCount; i++) {
-                        PrinterInfo printer = printers.get(i);
-
-                        serializer.startTag(null, TAG_PRINTER);
-
-                        serializer.attribute(null, ATTR_NAME, printer.getName());
-                        // Historical printers are always stored as unavailable.
-                        serializer.attribute(null, ATTR_STATUS, String.valueOf(
-                                PrinterInfo.STATUS_UNAVAILABLE));
-                        String description = printer.getDescription();
-                        if (description != null) {
-                            serializer.attribute(null, ATTR_DESCRIPTION, description);
-                        }
-
-                        PrinterId printerId = printer.getId();
-                        serializer.startTag(null, TAG_PRINTER_ID);
-                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
-                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
-                                .flattenToString());
-                        serializer.endTag(null, TAG_PRINTER_ID);
-
-                        serializer.endTag(null, TAG_PRINTER);
-
-                        if (DEBUG) {
-                            Log.i(LOG_TAG, "[PERSISTED] " + printer);
-                        }
-                    }
-
-                    serializer.endTag(null, TAG_PRINTERS);
-                    serializer.endDocument();
-                    mStatePersistFile.finishWrite(out);
-
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "[PERSIST END]");
-                    }
-                } catch (IOException ioe) {
-                    Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
-                    mStatePersistFile.failWrite(out);
-                } finally {
-                    IoUtils.closeQuietly(out);
-                }
-            }
-        };
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
deleted file mode 100644
index ac27562..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
+++ /dev/null
@@ -1,99 +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.print.PrintAttributes.MediaSize;
-import android.util.ArrayMap;
-
-import java.util.Comparator;
-import java.util.Map;
-
-/**
- * Utility functions and classes for dealing with media sizes.
- */
-public class MediaSizeUtils {
-
-    private static Map<MediaSize, String> sMediaSizeToStandardMap;
-
-    /**
-     * Gets the default media size for the current locale.
-     *
-     * @param context Context for accessing resources.
-     * @return The default media size.
-     */
-    public static MediaSize getDefault(Context context) {
-        String mediaSizeId = context.getString(R.string.mediasize_default);
-        return MediaSize.getStandardMediaSizeById(mediaSizeId);
-    }
-
-    private static String getStandardForMediaSize(Context context, MediaSize mediaSize) {
-        if (sMediaSizeToStandardMap == null) {
-            sMediaSizeToStandardMap = new ArrayMap<MediaSize, String>();
-            String[] mediaSizeToStandardMapValues = context.getResources()
-                    .getStringArray(R.array.mediasize_to_standard_map);
-            final int mediaSizeToStandardCount = mediaSizeToStandardMapValues.length;
-            for (int i = 0; i < mediaSizeToStandardCount; i += 2) {
-                String mediaSizeId = mediaSizeToStandardMapValues[i];
-                MediaSize key = MediaSize.getStandardMediaSizeById(mediaSizeId);
-                String value = mediaSizeToStandardMapValues[i + 1];
-                sMediaSizeToStandardMap.put(key, value);
-            }
-        }
-        String standard = sMediaSizeToStandardMap.get(mediaSize);
-        return (standard != null) ? standard : context.getString(
-                R.string.mediasize_standard_iso);
-    }
-
-    /**
-     * Comparator for ordering standard media sizes. The ones for the current
-     * standard go to the top and the ones for the other standards follow grouped
-     * by standard. Media sizes of the same standard are ordered alphabetically.
-     */
-    public static final class MediaSizeComparator implements Comparator<MediaSize> {
-        private final Context mContext;
-
-        public MediaSizeComparator(Context context) {
-            mContext = context;
-        }
-
-        @Override
-        public int compare(MediaSize lhs, MediaSize rhs) {
-            String currentStandard = mContext.getString(R.string.mediasize_standard);
-            String lhsStandard = getStandardForMediaSize(mContext, lhs);
-            String rhsStandard = getStandardForMediaSize(mContext, rhs);
-
-            // The current standard always wins.
-            if (lhsStandard.equals(currentStandard)) {
-                if (!rhsStandard.equals(currentStandard)) {
-                    return -1;
-                }
-            } else if (rhsStandard.equals(currentStandard)) {
-                return 1;
-            }
-
-            if (!lhsStandard.equals(rhsStandard)) {
-                // Different standards - use the standard ordering.
-                return lhsStandard.compareTo(rhsStandard);
-            } else {
-                // Same standard - sort alphabetically by label.
-                return lhs.getLabel(mContext.getPackageManager()).
-                        compareTo(rhs.getLabel(mContext.getPackageManager()));
-            }
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
deleted file mode 100644
index 968a8bf..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
+++ /dev/null
@@ -1,389 +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.Notification;
-import android.app.Notification.InboxStyle;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.print.IPrintManager;
-import android.print.PrintJobId;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is responsible for updating the print notifications
- * based on print job state transitions.
- */
-public class NotificationController {
-    public static final boolean DEBUG = false;
-
-    public static final String LOG_TAG = "NotificationController";
-
-    private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
-    private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
-
-    private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
-    private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
-    private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
-
-    private final Context mContext;
-    private final NotificationManager mNotificationManager;
-
-    public NotificationController(Context context) {
-        mContext = context;
-        mNotificationManager = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-
-    public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
-        List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
-
-        final int printJobCount = printJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = printJobs.get(i);
-            if (shouldNotifyForState(printJob.getState())) {
-                notifyPrintJobs.add(printJob);
-            }
-        }
-
-        updateNotification(notifyPrintJobs);
-    }
-
-    private void updateNotification(List<PrintJobInfo> printJobs) {
-        if (printJobs.size() <= 0) {
-            removeNotification();
-        } else if (printJobs.size() == 1) {
-            createSimpleNotification(printJobs.get(0));
-        } else {
-            createStackedNotification(printJobs);
-        }
-    }
-
-    private void createSimpleNotification(PrintJobInfo printJob) {
-        switch (printJob.getState()) {
-            case PrintJobInfo.STATE_FAILED: {
-                createFailedNotification(printJob);
-            } break;
-
-            case PrintJobInfo.STATE_BLOCKED: {
-                if (!printJob.isCancelling()) {
-                    createBlockedNotification(printJob);
-                } else {
-                    createCancellingNotification(printJob);
-                }
-            } break;
-
-            default: {
-                if (!printJob.isCancelling()) {
-                    createPrintingNotification(printJob);
-                } else {
-                    createCancellingNotification(printJob);
-                }
-            } break;
-        }
-    }
-
-    private void createPrintingNotification(PrintJobInfo printJob) {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(computeNotificationIcon(printJob))
-                .setContentTitle(computeNotificationTitle(printJob))
-                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
-                        createCancelIntent(printJob))
-                .setContentText(printJob.getPrinterName())
-                .setWhen(System.currentTimeMillis())
-                .setOngoing(true)
-                .setShowWhen(true);
-        mNotificationManager.notify(0, builder.build());
-    }
-
-    private void createFailedNotification(PrintJobInfo printJob) {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(computeNotificationIcon(printJob))
-                .setContentTitle(computeNotificationTitle(printJob))
-                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
-                        createCancelIntent(printJob))
-                .addAction(R.drawable.ic_restart, mContext.getString(R.string.restart),
-                        createRestartIntent(printJob.getId()))
-                .setContentText(printJob.getPrinterName())
-                .setWhen(System.currentTimeMillis())
-                .setOngoing(true)
-                .setShowWhen(true);
-        mNotificationManager.notify(0, builder.build());
-    }
-
-    private void createBlockedNotification(PrintJobInfo printJob) {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(computeNotificationIcon(printJob))
-                .setContentTitle(computeNotificationTitle(printJob))
-                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
-                        createCancelIntent(printJob))
-                .setContentText(printJob.getPrinterName())
-                .setWhen(System.currentTimeMillis())
-                .setOngoing(true)
-                .setShowWhen(true);
-           mNotificationManager.notify(0, builder.build());
-    }
-
-    private void createCancellingNotification(PrintJobInfo printJob) {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(computeNotificationIcon(printJob))
-                .setContentTitle(computeNotificationTitle(printJob))
-                .setContentText(printJob.getPrinterName())
-                .setWhen(System.currentTimeMillis())
-                .setOngoing(true)
-                .setShowWhen(true);
-        mNotificationManager.notify(0, builder.build());
-    }
-
-    private void createStackedNotification(List<PrintJobInfo> printJobs) {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setContentIntent(createContentIntent(null))
-                .setWhen(System.currentTimeMillis())
-                .setOngoing(true)
-                .setShowWhen(true);
-
-        final int printJobCount = printJobs.size();
-
-        InboxStyle inboxStyle = new InboxStyle();
-        inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
-                R.plurals.composite_notification_title_template,
-                printJobCount).toString(), printJobCount));
-
-        for (int i = printJobCount - 1; i>= 0; i--) {
-            PrintJobInfo printJob = printJobs.get(i);
-            if (i == printJobCount - 1) {
-                builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
-                        computeNotificationIcon(printJob))).getBitmap());
-                builder.setSmallIcon(computeNotificationIcon(printJob));
-                builder.setContentTitle(computeNotificationTitle(printJob));
-                builder.setContentText(printJob.getPrinterName());
-            }
-            inboxStyle.addLine(computeNotificationTitle(printJob));
-        }
-
-        builder.setNumber(printJobCount);
-        builder.setStyle(inboxStyle);
-
-        mNotificationManager.notify(0, builder.build());
-    }
-
-    private String computeNotificationTitle(PrintJobInfo printJob) {
-        switch (printJob.getState()) {
-            case PrintJobInfo.STATE_FAILED: {
-                return mContext.getString(R.string.failed_notification_title_template,
-                        printJob.getLabel());
-            }
-
-            case PrintJobInfo.STATE_BLOCKED: {
-                if (!printJob.isCancelling()) {
-                    return mContext.getString(R.string.blocked_notification_title_template,
-                            printJob.getLabel());
-                } else {
-                    return mContext.getString(
-                            R.string.cancelling_notification_title_template,
-                            printJob.getLabel());
-                }
-            }
-
-            default: {
-                if (!printJob.isCancelling()) {
-                    return mContext.getString(R.string.printing_notification_title_template,
-                            printJob.getLabel());
-                } else {
-                    return mContext.getString(
-                            R.string.cancelling_notification_title_template,
-                            printJob.getLabel());
-                }
-            }
-        }
-    }
-
-    private void removeNotification() {
-        mNotificationManager.cancel(0);
-    }
-
-    private PendingIntent createContentIntent(PrintJobId printJobId) {
-        Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
-        if (printJobId != null) {
-            intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
-            intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null));
-        }
-        return PendingIntent.getActivity(mContext, 0, intent, 0);
-    }
-
-    private PendingIntent createCancelIntent(PrintJobInfo printJob) {
-        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
-        intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
-        intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
-        intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
-        intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
-    }
-
-    private PendingIntent createRestartIntent(PrintJobId printJobId) {
-        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
-        intent.setAction(INTENT_ACTION_RESTART_PRINTJOB + "_" + printJobId.flattenToString());
-        intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId);
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
-    }
-
-    private static boolean shouldNotifyForState(int state) {
-        switch (state) {
-            case PrintJobInfo.STATE_QUEUED:
-            case PrintJobInfo.STATE_STARTED:
-            case PrintJobInfo.STATE_FAILED:
-            case PrintJobInfo.STATE_COMPLETED:
-            case PrintJobInfo.STATE_CANCELED:
-            case PrintJobInfo.STATE_BLOCKED: {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static int computeNotificationIcon(PrintJobInfo printJob) {
-        switch (printJob.getState()) {
-            case PrintJobInfo.STATE_FAILED:
-            case PrintJobInfo.STATE_BLOCKED: {
-                return com.android.internal.R.drawable.ic_print_error;
-            }
-            default: {
-                if (!printJob.isCancelling()) {
-                    return com.android.internal.R.drawable.ic_print;
-                } else {
-                    return R.drawable.stat_notify_cancelling;
-                }
-            }
-        }
-    }
-
-    public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
-        private static final String LOG_TAG = "NotificationBroadcastReceiver";
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
-                PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
-                String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
-                String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
-                handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
-            } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
-                PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
-                handleRestartPrintJob(context, printJobId);
-            }
-        }
-
-        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
-                final String printJobLabel, final String printerName) {
-            if (DEBUG) {
-                Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
-            }
-
-            // Call into the print manager service off the main thread since
-            // the print manager service may end up binding to the print spooler
-            // service which binding is handled on the main thread.
-            PowerManager powerManager = (PowerManager)
-                    context.getSystemService(Context.POWER_SERVICE);
-            final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    LOG_TAG);
-            wakeLock.acquire();
-
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    // We need to request the cancellation to be done by the print
-                    // manager service since it has to communicate with the managing
-                    // print service to request the cancellation. Also we need the
-                    // system service to be bound to the spooler since canceling a
-                    // print job will trigger persistence of current jobs which is
-                    // done on another thread and until it finishes the spooler has
-                    // to be kept around.
-                    try {
-                        IPrintManager printManager = IPrintManager.Stub.asInterface(
-                                ServiceManager.getService(Context.PRINT_SERVICE));
-                        printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
-                                UserHandle.myUserId());
-                    } catch (RemoteException re) {
-                        Log.i(LOG_TAG, "Error requestion print job cancellation", re);
-                    } finally {
-                        wakeLock.release();
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-        }
-
-        private void handleRestartPrintJob(final Context context, final PrintJobId printJobId) {
-            if (DEBUG) {
-                Log.i(LOG_TAG, "handleRestartPrintJob() printJobId:" + printJobId);
-            }
-
-            // Call into the print manager service off the main thread since
-            // the print manager service may end up binding to the print spooler
-            // service which binding is handled on the main thread.
-            PowerManager powerManager = (PowerManager)
-                    context.getSystemService(Context.POWER_SERVICE);
-            final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    LOG_TAG);
-            wakeLock.acquire();
-
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    // We need to request the restart to be done by the print manager
-                    // service since the latter must be bound to the spooler because
-                    // restarting a print job will trigger persistence of current jobs
-                    // which is done on another thread and until it finishes the spooler has
-                    // to be kept around.
-                    try {
-                        IPrintManager printManager = IPrintManager.Stub.asInterface(
-                                ServiceManager.getService(Context.PRINT_SERVICE));
-                        printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY,
-                                UserHandle.myUserId());
-                    } catch (RemoteException re) {
-                        Log.i(LOG_TAG, "Error requestion print job restart", re);
-                    } finally {
-                        wakeLock.release();
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-        }
-    }
-}
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/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
deleted file mode 100644
index 615d667..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ /dev/null
@@ -1,1337 +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.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.print.IPrintSpooler;
-import android.print.IPrintSpoolerCallbacks;
-import android.print.IPrintSpoolerClient;
-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.PrintDocumentInfo;
-import android.print.PrintJobId;
-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;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.util.FastXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Service for exposing some of the {@link PrintSpooler} functionality to
- * another process.
- */
-public final class PrintSpoolerService extends Service {
-
-    private static final String LOG_TAG = "PrintSpoolerService";
-
-    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
-
-    private static final boolean DEBUG_PERSISTENCE = false;
-
-    private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
-
-    private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
-
-    private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
-
-    private static final String PRINT_FILE_EXTENSION = "pdf";
-
-    private static final Object sLock = new Object();
-
-    private final Object mLock = new Object();
-
-    private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
-
-    private static PrintSpoolerService sInstance;
-
-    private IPrintSpoolerClient mClient;
-
-    private HandlerCaller mHandlerCaller;
-
-    private PersistenceManager mPersistanceManager;
-
-    private NotificationController mNotificationController;
-
-    public static PrintSpoolerService peekInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mHandlerCaller = new HandlerCaller(this, getMainLooper(),
-                new HandlerCallerCallback(), false);
-
-        mPersistanceManager = new PersistenceManager();
-        mNotificationController = new NotificationController(PrintSpoolerService.this);
-
-        synchronized (mLock) {
-            mPersistanceManager.readStateLocked();
-            handleReadPrintJobsLocked();
-        }
-
-        synchronized (sLock) {
-            sInstance = this;
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return new PrintSpooler();
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        synchronized (mLock) {
-            String prefix = (args.length > 0) ? args[0] : "";
-            String tab = "  ";
-
-            pw.append(prefix).append("print jobs:").println();
-            final int printJobCount = mPrintJobs.size();
-            for (int i = 0; i < printJobCount; i++) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                pw.append(prefix).append(tab).append(printJob.toString());
-                pw.println();
-            }
-
-            pw.append(prefix).append("print job files:").println();
-            File[] files = getFilesDir().listFiles();
-            if (files != null) {
-                final int fileCount = files.length;
-                for (int i = 0; i < fileCount; i++) {
-                    File file = files[i];
-                    if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
-                        pw.append(prefix).append(tab).append(file.getName()).println();
-                    }
-                }
-            }
-        }
-    }
-
-    private void sendOnPrintJobQueued(PrintJobInfo printJob) {
-        Message message = mHandlerCaller.obtainMessageO(
-                HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
-        mHandlerCaller.executeOrSendMessage(message);
-    }
-
-    private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
-        Message message = mHandlerCaller.obtainMessageO(
-                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
-        mHandlerCaller.executeOrSendMessage(message);
-    }
-
-    private void sendOnAllPrintJobsHandled() {
-        Message message = mHandlerCaller.obtainMessage(
-                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
-        mHandlerCaller.executeOrSendMessage(message);
-    }
-
-    private final class HandlerCallerCallback implements HandlerCaller.Callback {
-        public static final int MSG_SET_CLIENT = 1;
-        public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
-        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
-        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
-        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
-        public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
-
-        @Override
-        public void executeMessage(Message message) {
-            switch (message.what) {
-                case MSG_SET_CLIENT: {
-                    synchronized (mLock) {
-                        mClient = (IPrintSpoolerClient) message.obj;
-                        if (mClient != null) {
-                            Message msg = mHandlerCaller.obtainMessage(
-                                    HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
-                            mHandlerCaller.sendMessageDelayed(msg,
-                                    CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
-                        }
-                    }
-                } break;
-
-                case MSG_ON_PRINT_JOB_QUEUED: {
-                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
-                    if (mClient != null) {
-                        try {
-                            mClient.onPrintJobQueued(printJob);
-                        } catch (RemoteException re) {
-                            Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
-                        }
-                    }
-                } break;
-
-                case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
-                    ComponentName service = (ComponentName) message.obj;
-                    if (mClient != null) {
-                        try {
-                            mClient.onAllPrintJobsForServiceHandled(service);
-                        } catch (RemoteException re) {
-                            Slog.e(LOG_TAG, "Error notify for all print jobs per service"
-                                    + " handled.", re);
-                        }
-                    }
-                } break;
-
-                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
-                    if (mClient != null) {
-                        try {
-                            mClient.onAllPrintJobsHandled();
-                        } catch (RemoteException re) {
-                            Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
-                        }
-                    }
-                } break;
-
-                case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
-                    checkAllPrintJobsHandled();
-                } break;
-
-                case MSG_ON_PRINT_JOB_STATE_CHANGED: {
-                    if (mClient != null) {
-                        PrintJobInfo printJob = (PrintJobInfo) message.obj;
-                        try {
-                            mClient.onPrintJobStateChanged(printJob);
-                        } catch (RemoteException re) {
-                            Slog.e(LOG_TAG, "Error notify for print job state change.", re);
-                        }
-                    }
-                } break;
-            }
-        }
-    }
-
-    public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
-            int state, int appId) {
-        List<PrintJobInfo> foundPrintJobs = null;
-        synchronized (mLock) {
-            final int printJobCount = mPrintJobs.size();
-            for (int i = 0; i < printJobCount; i++) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                PrinterId printerId = printJob.getPrinterId();
-                final boolean sameComponent = (componentName == null
-                        || (printerId != null
-                        && componentName.equals(printerId.getServiceName())));
-                final boolean sameAppId = appId == PrintManager.APP_ID_ANY
-                        || printJob.getAppId() == appId;
-                final boolean sameState = (state == printJob.getState())
-                        || (state == PrintJobInfo.STATE_ANY)
-                        || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
-                            && isStateVisibleToUser(printJob.getState()))
-                        || (state == PrintJobInfo.STATE_ANY_ACTIVE
-                            && isActiveState(printJob.getState()))
-                        || (state == PrintJobInfo.STATE_ANY_SCHEDULED
-                            && isScheduledState(printJob.getState()));
-                if (sameComponent && sameAppId && sameState) {
-                    if (foundPrintJobs == null) {
-                        foundPrintJobs = new ArrayList<PrintJobInfo>();
-                    }
-                    foundPrintJobs.add(printJob);
-                }
-            }
-        }
-        return foundPrintJobs;
-    }
-
-    private boolean isStateVisibleToUser(int state) {
-        return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
-                || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
-                || state == PrintJobInfo.STATE_BLOCKED));
-    }
-
-    public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
-        synchronized (mLock) {
-            final int printJobCount = mPrintJobs.size();
-            for (int i = 0; i < printJobCount; i++) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                if (printJob.getId().equals(printJobId)
-                        && (appId == PrintManager.APP_ID_ANY
-                        || appId == printJob.getAppId())) {
-                    return printJob;
-                }
-            }
-            return null;
-        }
-    }
-
-    public void createPrintJob(PrintJobInfo printJob) {
-        synchronized (mLock) {
-            addPrintJobLocked(printJob);
-            setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
-
-            Message message = mHandlerCaller.obtainMessageO(
-                    HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                    printJob);
-            mHandlerCaller.executeOrSendMessage(message);
-        }
-    }
-
-    private void handleReadPrintJobsLocked() {
-        // Make a map with the files for a print job since we may have
-        // to delete some. One example of getting orphan files if the
-        // spooler crashes while constructing a print job. We do not
-        // persist partially populated print jobs under construction to
-        // avoid special handling for various attributes missing.
-        ArrayMap<PrintJobId, File> fileForJobMap = null;
-        File[] files = getFilesDir().listFiles();
-        if (files != null) {
-            final int fileCount = files.length;
-            for (int i = 0; i < fileCount; i++) {
-                File file = files[i];
-                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
-                    if (fileForJobMap == null) {
-                        fileForJobMap = new ArrayMap<PrintJobId, File>();
-                    }
-                    String printJobIdString = file.getName().substring(
-                            PRINT_JOB_FILE_PREFIX.length(),
-                            file.getName().indexOf('.'));
-                    PrintJobId printJobId = PrintJobId.unflattenFromString(
-                            printJobIdString);
-                    fileForJobMap.put(printJobId, file);
-                }
-            }
-        }
-
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-
-            // We want to have only the orphan files at the end.
-            if (fileForJobMap != null) {
-                fileForJobMap.remove(printJob.getId());
-            }
-
-            switch (printJob.getState()) {
-                case PrintJobInfo.STATE_QUEUED:
-                case PrintJobInfo.STATE_STARTED:
-                case PrintJobInfo.STATE_BLOCKED: {
-                    // We have a print job that was queued or started or blocked in
-                    // the past but the device battery died or a crash occurred. In
-                    // this case we assume the print job failed and let the user
-                    // decide whether to restart the job or just cancel it.
-                    setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
-                            getString(R.string.no_connection_to_printer));
-                } break;
-            }
-        }
-
-        if (!mPrintJobs.isEmpty()) {
-            // Update the notification.
-            mNotificationController.onUpdateNotifications(mPrintJobs);
-        }
-
-        // Delete the orphan files.
-        if (fileForJobMap != null) {
-            final int orphanFileCount = fileForJobMap.size();
-            for (int i = 0; i < orphanFileCount; i++) {
-                File file = fileForJobMap.valueAt(i);
-                file.delete();
-            }
-        }
-    }
-
-    public void checkAllPrintJobsHandled() {
-        synchronized (mLock) {
-            if (!hasActivePrintJobsLocked()) {
-                notifyOnAllPrintJobsHandled();
-            }
-        }
-    }
-
-    public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
-        final PrintJobInfo printJob;
-        synchronized (mLock) {
-            printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-        }
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                FileInputStream in = null;
-                FileOutputStream out = null;
-                try {
-                    if (printJob != null) {
-                        File file = generateFileForPrintJob(printJobId);
-                        in = new FileInputStream(file);
-                        out = new FileOutputStream(fd.getFileDescriptor());
-                    }
-                    final byte[] buffer = new byte[8192];
-                    while (true) {
-                        final int readByteCount = in.read(buffer);
-                        if (readByteCount < 0) {
-                            return null;
-                        }
-                        out.write(buffer, 0, readByteCount);
-                    }
-                } catch (FileNotFoundException fnfe) {
-                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
-                } catch (IOException ioe) {
-                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
-                } finally {
-                    IoUtils.closeQuietly(in);
-                    IoUtils.closeQuietly(out);
-                    IoUtils.closeQuietly(fd);
-                }
-                Log.i(LOG_TAG, "[END WRITE]");
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    public File generateFileForPrintJob(PrintJobId printJobId) {
-        return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
-                + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
-    }
-
-    private void addPrintJobLocked(PrintJobInfo printJob) {
-        mPrintJobs.add(printJob);
-        if (DEBUG_PRINT_JOB_LIFECYCLE) {
-            Slog.i(LOG_TAG, "[ADD] " + printJob);
-        }
-    }
-
-    private void removeObsoletePrintJobs() {
-        synchronized (mLock) {
-            boolean persistState = false;
-            final int printJobCount = mPrintJobs.size();
-            for (int i = printJobCount - 1; i >= 0; i--) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                if (isObsoleteState(printJob.getState())) {
-                    mPrintJobs.remove(i);
-                    if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                        Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
-                    }
-                    removePrintJobFileLocked(printJob.getId());
-                    persistState = true;
-                }
-            }
-            if (persistState) {
-                mPersistanceManager.writeStateLocked();
-            }
-        }
-    }
-
-    private void removePrintJobFileLocked(PrintJobId printJobId) {
-        File file = generateFileForPrintJob(printJobId);
-        if (file.exists()) {
-            file.delete();
-            if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
-            }
-        }
-    }
-
-    public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
-        boolean success = false;
-
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                final int oldState = printJob.getState();
-                if (oldState == state) {
-                    return false;
-                }
-
-                success = true;
-
-                printJob.setState(state);
-                printJob.setStateReason(error);
-                printJob.setCancelling(false);
-
-                if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                    Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
-                }
-
-                switch (state) {
-                    case PrintJobInfo.STATE_COMPLETED:
-                    case PrintJobInfo.STATE_CANCELED:
-                        mPrintJobs.remove(printJob);
-                        removePrintJobFileLocked(printJob.getId());
-                        // $fall-through$
-
-                    case PrintJobInfo.STATE_FAILED: {
-                        PrinterId printerId = printJob.getPrinterId();
-                        if (printerId != null) {
-                            ComponentName service = printerId.getServiceName();
-                            if (!hasActivePrintJobsForServiceLocked(service)) {
-                                sendOnAllPrintJobsForServiceHandled(service);
-                            }
-                        }
-                    } break;
-
-                    case PrintJobInfo.STATE_QUEUED: {
-                        sendOnPrintJobQueued(new PrintJobInfo(printJob));
-                    }  break;
-                }
-
-                if (shouldPersistPrintJob(printJob)) {
-                    mPersistanceManager.writeStateLocked();
-                }
-
-                if (!hasActivePrintJobsLocked()) {
-                    notifyOnAllPrintJobsHandled();
-                }
-
-                Message message = mHandlerCaller.obtainMessageO(
-                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob);
-                mHandlerCaller.executeOrSendMessage(message);
-
-                mNotificationController.onUpdateNotifications(mPrintJobs);
-            }
-        }
-
-        return success;
-    }
-
-    public boolean hasActivePrintJobsLocked() {
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-            if (isActiveState(printJob.getState())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-            if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null
-                    && printJob.getPrinterId().getServiceName().equals(service)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean isObsoleteState(int printJobState) {
-        return (isTeminalState(printJobState)
-                || printJobState == PrintJobInfo.STATE_QUEUED);
-    }
-
-    private boolean isScheduledState(int printJobState) {
-        return printJobState == PrintJobInfo.STATE_QUEUED
-                || printJobState == PrintJobInfo.STATE_STARTED
-                || printJobState == PrintJobInfo.STATE_BLOCKED;
-    }
-
-    private boolean isActiveState(int printJobState) {
-        return printJobState == PrintJobInfo.STATE_CREATED
-                || printJobState == PrintJobInfo.STATE_QUEUED
-                || printJobState == PrintJobInfo.STATE_STARTED
-                || printJobState == PrintJobInfo.STATE_BLOCKED;
-    }
-
-    private boolean isTeminalState(int printJobState) {
-        return printJobState == PrintJobInfo.STATE_COMPLETED
-                || printJobState == PrintJobInfo.STATE_CANCELED;
-    }
-
-    public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                String printJobTag = printJob.getTag();
-                if (printJobTag == null) {
-                    if (tag == null) {
-                        return false;
-                    }
-                } else if (printJobTag.equals(tag)) {
-                    return false;
-                }
-                printJob.setTag(tag);
-                if (shouldPersistPrintJob(printJob)) {
-                    mPersistanceManager.writeStateLocked();
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setCancelling(cancelling);
-                if (shouldPersistPrintJob(printJob)) {
-                    mPersistanceManager.writeStateLocked();
-                }
-                mNotificationController.onUpdateNotifications(mPrintJobs);
-
-                Message message = mHandlerCaller.obtainMessageO(
-                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob);
-                mHandlerCaller.executeOrSendMessage(message);
-            }
-        }
-    }
-
-    public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setCopies(copies);
-            }
-        }
-    }
-
-    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);
-            }
-        }
-    }
-
-    private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
-        return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
-    }
-
-    private void notifyOnAllPrintJobsHandled() {
-        // This has to run on the tread that is persisting the current state
-        // since this call may result in the system unbinding from the spooler
-        // and as a result the spooler process may get killed before the write
-        // completes.
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                sendOnAllPrintJobsHandled();
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    private final class PersistenceManager {
-        private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
-
-        private static final String TAG_SPOOLER = "spooler";
-        private static final String TAG_JOB = "job";
-
-        private static final String TAG_PRINTER_ID = "printerId";
-        private static final String TAG_PAGE_RANGE = "pageRange";
-        private static final String TAG_ATTRIBUTES = "attributes";
-        private static final String TAG_DOCUMENT_INFO = "documentInfo";
-
-        private static final String ATTR_ID = "id";
-        private static final String ATTR_LABEL = "label";
-        private static final String ATTR_LABEL_RES_ID = "labelResId";
-        private static final String ATTR_PACKAGE_NAME = "packageName";
-        private static final String ATTR_STATE = "state";
-        private static final String ATTR_APP_ID = "appId";
-        private static final String ATTR_TAG = "tag";
-        private static final String ATTR_CREATION_TIME = "creationTime";
-        private static final String ATTR_COPIES = "copies";
-        private static final String ATTR_PRINTER_NAME = "printerName";
-        private static final String ATTR_STATE_REASON = "stateReason";
-        private static final String ATTR_CANCELLING = "cancelling";
-
-        private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
-        private static final String TAG_ADVANCED_OPTION = "advancedOption";
-        private static final String ATTR_KEY = "key";
-        private static final String ATTR_TYPE = "type";
-        private static final String ATTR_VALUE = "value";
-        private static final String TYPE_STRING = "string";
-        private static final String TYPE_INT = "int";
-
-        private static final String TAG_MEDIA_SIZE = "mediaSize";
-        private static final String TAG_RESOLUTION = "resolution";
-        private static final String TAG_MARGINS = "margins";
-
-        private static final String ATTR_COLOR_MODE = "colorMode";
-
-        private static final String ATTR_LOCAL_ID = "localId";
-        private static final String ATTR_SERVICE_NAME = "serviceName";
-
-        private static final String ATTR_WIDTH_MILS = "widthMils";
-        private static final String ATTR_HEIGHT_MILS = "heightMils";
-
-        private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
-        private static final String ATTR_VERTICAL_DPI = "verticalDpi";
-
-        private static final String ATTR_LEFT_MILS = "leftMils";
-        private static final String ATTR_TOP_MILS = "topMils";
-        private static final String ATTR_RIGHT_MILS = "rightMils";
-        private static final String ATTR_BOTTOM_MILS = "bottomMils";
-
-        private static final String ATTR_START = "start";
-        private static final String ATTR_END = "end";
-
-        private static final String ATTR_NAME = "name";
-        private static final String ATTR_PAGE_COUNT = "pageCount";
-        private static final String ATTR_CONTENT_TYPE = "contentType";
-        private static final String ATTR_DATA_SIZE = "dataSize";
-
-        private final AtomicFile mStatePersistFile;
-
-        private boolean mWriteStateScheduled;
-
-        private PersistenceManager() {
-            mStatePersistFile = new AtomicFile(new File(getFilesDir(),
-                    PERSIST_FILE_NAME));
-        }
-
-        public void writeStateLocked() {
-            if (!PERSISTNECE_MANAGER_ENABLED) {
-                return;
-            }
-            if (mWriteStateScheduled) {
-                return;
-            }
-            mWriteStateScheduled = true;
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (mLock) {
-                        mWriteStateScheduled = false;
-                        doWriteStateLocked();
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-        }
-
-        private void doWriteStateLocked() {
-            if (DEBUG_PERSISTENCE) {
-                Log.i(LOG_TAG, "[PERSIST START]");
-            }
-            FileOutputStream out = null;
-            try {
-                out = mStatePersistFile.startWrite();
-
-                XmlSerializer serializer = new FastXmlSerializer();
-                serializer.setOutput(out, "utf-8");
-                serializer.startDocument(null, true);
-                serializer.startTag(null, TAG_SPOOLER);
-
-                List<PrintJobInfo> printJobs = mPrintJobs;
-
-                final int printJobCount = printJobs.size();
-                for (int j = 0; j < printJobCount; j++) {
-                    PrintJobInfo printJob = printJobs.get(j);
-
-                    if (!shouldPersistPrintJob(printJob)) {
-                        continue;
-                    }
-
-                    serializer.startTag(null, TAG_JOB);
-
-                    serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
-                    serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
-                    serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
-                    serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
-                    String tag = printJob.getTag();
-                    if (tag != null) {
-                        serializer.attribute(null, ATTR_TAG, tag);
-                    }
-                    serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
-                            printJob.getCreationTime()));
-                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
-                    String printerName = printJob.getPrinterName();
-                    if (!TextUtils.isEmpty(printerName)) {
-                        serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
-                    }
-                    String stateReason = printJob.getStateReason();
-                    if (!TextUtils.isEmpty(stateReason)) {
-                        serializer.attribute(null, ATTR_STATE_REASON, stateReason);
-                    }
-                    serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
-                            printJob.isCancelling()));
-
-                    PrinterId printerId = printJob.getPrinterId();
-                    if (printerId != null) {
-                        serializer.startTag(null, TAG_PRINTER_ID);
-                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
-                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
-                                .flattenToString());
-                        serializer.endTag(null, TAG_PRINTER_ID);
-                    }
-
-                    PageRange[] pages = printJob.getPages();
-                    if (pages != null) {
-                        for (int i = 0; i < pages.length; i++) {
-                            serializer.startTag(null, TAG_PAGE_RANGE);
-                            serializer.attribute(null, ATTR_START, String.valueOf(
-                                    pages[i].getStart()));
-                            serializer.attribute(null, ATTR_END, String.valueOf(
-                                    pages[i].getEnd()));
-                            serializer.endTag(null, TAG_PAGE_RANGE);
-                        }
-                    }
-
-                    PrintAttributes attributes = printJob.getAttributes();
-                    if (attributes != null) {
-                        serializer.startTag(null, TAG_ATTRIBUTES);
-
-                        final int colorMode = attributes.getColorMode();
-                        serializer.attribute(null, ATTR_COLOR_MODE,
-                                String.valueOf(colorMode));
-
-                        MediaSize mediaSize = attributes.getMediaSize();
-                        if (mediaSize != null) {
-                            serializer.startTag(null, TAG_MEDIA_SIZE);
-                            serializer.attribute(null, ATTR_ID, mediaSize.getId());
-                            serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
-                                    mediaSize.getWidthMils()));
-                            serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
-                                    mediaSize.getHeightMils()));
-                            // We prefer to store only the package name and
-                            // resource id and fallback to the label.
-                            if (!TextUtils.isEmpty(mediaSize.mPackageName)
-                                    && mediaSize.mLabelResId > 0) {
-                                serializer.attribute(null, ATTR_PACKAGE_NAME,
-                                        mediaSize.mPackageName);
-                                serializer.attribute(null, ATTR_LABEL_RES_ID,
-                                        String.valueOf(mediaSize.mLabelResId));
-                            } else {
-                                serializer.attribute(null, ATTR_LABEL,
-                                        mediaSize.getLabel(getPackageManager()));
-                            }
-                            serializer.endTag(null, TAG_MEDIA_SIZE);
-                        }
-
-                        Resolution resolution = attributes.getResolution();
-                        if (resolution != null) {
-                            serializer.startTag(null, TAG_RESOLUTION);
-                            serializer.attribute(null, ATTR_ID, resolution.getId());
-                            serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
-                                    resolution.getHorizontalDpi()));
-                            serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
-                                    resolution.getVerticalDpi()));
-                            serializer.attribute(null, ATTR_LABEL,
-                                    resolution.getLabel());
-                            serializer.endTag(null, TAG_RESOLUTION);
-                        }
-
-                        Margins margins = attributes.getMinMargins();
-                        if (margins != null) {
-                            serializer.startTag(null, TAG_MARGINS);
-                            serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
-                                    margins.getLeftMils()));
-                            serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
-                                    margins.getTopMils()));
-                            serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
-                                    margins.getRightMils()));
-                            serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
-                                    margins.getBottomMils()));
-                            serializer.endTag(null, TAG_MARGINS);
-                        }
-
-                        serializer.endTag(null, TAG_ATTRIBUTES);
-                    }
-
-                    PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
-                    if (documentInfo != null) {
-                        serializer.startTag(null, TAG_DOCUMENT_INFO);
-                        serializer.attribute(null, ATTR_NAME, documentInfo.getName());
-                        serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
-                                documentInfo.getContentType()));
-                        serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
-                                documentInfo.getPageCount()));
-                        serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
-                                documentInfo.getDataSize()));
-                        serializer.endTag(null, TAG_DOCUMENT_INFO);
-                    }
-
-                    Bundle advancedOptions = printJob.getAdvancedOptions();
-                    if (advancedOptions != null) {
-                        serializer.startTag(null, TAG_ADVANCED_OPTIONS);
-                        for (String key : advancedOptions.keySet()) {
-                            Object value = advancedOptions.get(key);
-                            if (value instanceof String) {
-                                String stringValue = (String) value;
-                                serializer.startTag(null, TAG_ADVANCED_OPTION);
-                                serializer.attribute(null, ATTR_KEY, key);
-                                serializer.attribute(null, ATTR_TYPE, TYPE_STRING);
-                                serializer.attribute(null, ATTR_VALUE, stringValue);
-                                serializer.endTag(null, TAG_ADVANCED_OPTION);
-                            } else if (value instanceof Integer) {
-                                String intValue = Integer.toString((Integer) value);
-                                serializer.startTag(null, TAG_ADVANCED_OPTION);
-                                serializer.attribute(null, ATTR_KEY, key);
-                                serializer.attribute(null, ATTR_TYPE, TYPE_INT);
-                                serializer.attribute(null, ATTR_VALUE, intValue);
-                                serializer.endTag(null, TAG_ADVANCED_OPTION);
-                            }
-                        }
-                        serializer.endTag(null, TAG_ADVANCED_OPTIONS);
-                    }
-
-                    serializer.endTag(null, TAG_JOB);
-
-                    if (DEBUG_PERSISTENCE) {
-                        Log.i(LOG_TAG, "[PERSISTED] " + printJob);
-                    }
-                }
-
-                serializer.endTag(null, TAG_SPOOLER);
-                serializer.endDocument();
-                mStatePersistFile.finishWrite(out);
-                if (DEBUG_PERSISTENCE) {
-                    Log.i(LOG_TAG, "[PERSIST END]");
-                }
-            } catch (IOException e) {
-                Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
-                mStatePersistFile.failWrite(out);
-            } finally {
-                IoUtils.closeQuietly(out);
-            }
-        }
-
-        public void readStateLocked() {
-            if (!PERSISTNECE_MANAGER_ENABLED) {
-                return;
-            }
-            FileInputStream in = null;
-            try {
-                in = mStatePersistFile.openRead();
-            } catch (FileNotFoundException e) {
-                Log.i(LOG_TAG, "No existing print spooler state.");
-                return;
-            }
-            try {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(in, null);
-                parseState(parser);
-            } catch (IllegalStateException ise) {
-                Slog.w(LOG_TAG, "Failed parsing ", ise);
-            } catch (NullPointerException npe) {
-                Slog.w(LOG_TAG, "Failed parsing ", npe);
-            } catch (NumberFormatException nfe) {
-                Slog.w(LOG_TAG, "Failed parsing ", nfe);
-            } catch (XmlPullParserException xppe) {
-                Slog.w(LOG_TAG, "Failed parsing ", xppe);
-            } catch (IOException ioe) {
-                Slog.w(LOG_TAG, "Failed parsing ", ioe);
-            } catch (IndexOutOfBoundsException iobe) {
-                Slog.w(LOG_TAG, "Failed parsing ", iobe);
-            } finally {
-                IoUtils.closeQuietly(in);
-            }
-        }
-
-        private void parseState(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            parser.next();
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
-            parser.next();
-
-            while (parsePrintJob(parser)) {
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
-        }
-
-        private boolean parsePrintJob(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            skipEmptyTextTags(parser);
-            if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
-                return false;
-            }
-
-            PrintJobInfo printJob = new PrintJobInfo();
-
-            PrintJobId printJobId = PrintJobId.unflattenFromString(
-                    parser.getAttributeValue(null, ATTR_ID));
-            printJob.setId(printJobId);
-            String label = parser.getAttributeValue(null, ATTR_LABEL);
-            printJob.setLabel(label);
-            final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
-            printJob.setState(state);
-            final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
-            printJob.setAppId(appId);
-            String tag = parser.getAttributeValue(null, ATTR_TAG);
-            printJob.setTag(tag);
-            String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
-            printJob.setCreationTime(Long.parseLong(creationTime));
-            String copies = parser.getAttributeValue(null, ATTR_COPIES);
-            printJob.setCopies(Integer.parseInt(copies));
-            String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
-            printJob.setPrinterName(printerName);
-            String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
-            printJob.setStateReason(stateReason);
-            String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
-            printJob.setCancelling(!TextUtils.isEmpty(cancelling)
-                    ? Boolean.parseBoolean(cancelling) : false);
-
-            parser.next();
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
-                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
-                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
-                        null, ATTR_SERVICE_NAME));
-                printJob.setPrinterId(new PrinterId(service, localId));
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            List<PageRange> pageRanges = null;
-            while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
-                final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
-                final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
-                PageRange pageRange = new PageRange(start, end);
-                if (pageRanges == null) {
-                    pageRanges = new ArrayList<PageRange>();
-                }
-                pageRanges.add(pageRange);
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
-                parser.next();
-                skipEmptyTextTags(parser);
-            }
-            if (pageRanges != null) {
-                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
-                pageRanges.toArray(pageRangesArray);
-                printJob.setPages(pageRangesArray);
-            }
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
-
-                PrintAttributes.Builder builder = new PrintAttributes.Builder();
-
-                String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
-                builder.setColorMode(Integer.parseInt(colorMode));
-
-                parser.next();
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_WIDTH_MILS));
-                    final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_HEIGHT_MILS));
-                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
-                    String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
-                    final int labelResId = (labelResIdString != null)
-                            ? Integer.parseInt(labelResIdString) : 0;
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    MediaSize mediaSize = new MediaSize(id, label, packageName,
-                                widthMils, heightMils, labelResId);
-                    builder.setMediaSize(mediaSize);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_HORIZONTAL_DPI));
-                    final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_VERTICAL_DPI));
-                    Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
-                    builder.setResolution(resolution);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
-                    final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_LEFT_MILS));
-                    final int topMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_TOP_MILS));
-                    final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_RIGHT_MILS));
-                    final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_BOTTOM_MILS));
-                    Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
-                    builder.setMinMargins(margins);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
-                    parser.next();
-                }
-
-                printJob.setAttributes(builder.build());
-
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTR_PAGE_COUNT));
-                final int contentType = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTR_CONTENT_TYPE));
-                final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTR_DATA_SIZE));
-                PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
-                        .setPageCount(pageCount)
-                        .setContentType(contentType).build();
-                printJob.setDocumentInfo(info);
-                info.setDataSize(dataSize);
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTIONS)) {
-                parser.next();
-                skipEmptyTextTags(parser);
-                Bundle advancedOptions = new Bundle();
-                while (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTION)) {
-                    String key = parser.getAttributeValue(null, ATTR_KEY);
-                    String value = parser.getAttributeValue(null, ATTR_VALUE);
-                    String type = parser.getAttributeValue(null, ATTR_TYPE);
-                    if (TYPE_STRING.equals(type)) {
-                        advancedOptions.putString(key, value);
-                    } else if (TYPE_INT.equals(type)) {
-                        advancedOptions.putInt(key, Integer.valueOf(value));
-                    }
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTION);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                }
-                printJob.setAdvancedOptions(advancedOptions);
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTIONS);
-                parser.next();
-            }
-
-            mPrintJobs.add(printJob);
-
-            if (DEBUG_PERSISTENCE) {
-                Log.i(LOG_TAG, "[RESTORED] " + printJob);
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_JOB);
-
-            return true;
-        }
-
-        private void expect(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (!accept(parser, type, tag)) {
-                throw new XmlPullParserException("Exepected event: " + type
-                        + " and tag: " + tag + " but got event: " + parser.getEventType()
-                        + " and tag:" + parser.getName());
-            }
-        }
-
-        private void skipEmptyTextTags(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            while (accept(parser, XmlPullParser.TEXT, null)
-                    && "\n".equals(parser.getText())) {
-                parser.next();
-            }
-        }
-
-        private boolean accept(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (parser.getEventType() != type) {
-                return false;
-            }
-            if (tag != null) {
-                if (!tag.equals(parser.getName())) {
-                    return false;
-                }
-            } else if (parser.getName() != null) {
-                return false;
-            }
-            return true;
-        }
-    }
-
-    final class PrintSpooler extends IPrintSpooler.Stub {
-        @Override
-        public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
-                ComponentName componentName, int state, int appId, int sequence)
-                throws RemoteException {
-            List<PrintJobInfo> printJobs = null;
-            try {
-                printJobs = PrintSpoolerService.this.getPrintJobInfos(
-                        componentName, state, appId);
-            } finally {
-                callback.onGetPrintJobInfosResult(printJobs, sequence);
-            }
-        }
-
-        @Override
-        public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
-                int appId, int sequence) throws RemoteException {
-            PrintJobInfo printJob = null;
-            try {
-                printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
-            } finally {
-                callback.onGetPrintJobInfoResult(printJob, sequence);
-            }
-        }
-
-        @Override
-        public void createPrintJob(PrintJobInfo printJob) {
-            PrintSpoolerService.this.createPrintJob(printJob);
-        }
-
-        @Override
-        public void setPrintJobState(PrintJobId printJobId, int state, String error,
-                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
-            boolean success = false;
-            try {
-                success = PrintSpoolerService.this.setPrintJobState(
-                        printJobId, state, error);
-            } finally {
-                callback.onSetPrintJobStateResult(success, sequece);
-            }
-        }
-
-        @Override
-        public void setPrintJobTag(PrintJobId printJobId, String tag,
-                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
-            boolean success = false;
-            try {
-                success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
-            } finally {
-                callback.onSetPrintJobTagResult(success, sequece);
-            }
-        }
-
-        @Override
-        public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
-            PrintSpoolerService.this.writePrintJobData(fd, printJobId);
-        }
-
-        @Override
-        public void setClient(IPrintSpoolerClient client) {
-            Message message = mHandlerCaller.obtainMessageO(
-                    HandlerCallerCallback.MSG_SET_CLIENT, client);
-            mHandlerCaller.executeOrSendMessage(message);
-        }
-
-        @Override
-        public void removeObsoletePrintJobs() {
-            PrintSpoolerService.this.removeObsoletePrintJobs();
-        }
-
-        @Override
-        protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-            PrintSpoolerService.this.dump(fd, writer, args);
-        }
-
-        @Override
-        public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
-            PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
-        }
-
-        public PrintSpoolerService getService() {
-            return PrintSpoolerService.this;
-        }
-    }
-}
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/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
deleted file mode 100644
index fe5920c..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
+++ /dev/null
@@ -1,638 +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.AlertDialog;
-import android.app.Dialog;
-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;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.print.PrintManager;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.printservice.PrintServiceInfo;
-import android.provider.Settings;
-import android.text.TextUtils;
-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;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.Filter;
-import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.SearchView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This is a fragment for selecting a printer.
- */
-public final class SelectPrinterFragment extends Fragment {
-
-    private static final String LOG_TAG = "SelectPrinterFragment";
-
-    private static final int LOADER_ID_PRINTERS_LOADER = 1;
-
-    private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
-            "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
-
-    private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
-            "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
-
-    private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
-
-    private final ArrayList<PrintServiceInfo> mAddPrinterServices =
-            new ArrayList<PrintServiceInfo>();
-
-    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);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View content = inflater.inflate(R.layout.select_printer_fragment, container, false);
-
-        // Hook up the list view.
-        mListView = (ListView) content.findViewById(android.R.id.list);
-        final DestinationAdapter adapter = new DestinationAdapter();
-        adapter.registerDataSetObserver(new DataSetObserver() {
-            @Override
-            public void onChanged() {
-                if (!getActivity().isFinishing() && adapter.getCount() <= 0) {
-                    updateEmptyView(adapter);
-                }
-            }
-
-            @Override
-            public void onInvalidated() {
-                if (!getActivity().isFinishing()) {
-                    updateEmptyView(adapter);
-                }
-            }
-        });
-        mListView.setAdapter(adapter);
-
-        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                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");
-                }
-            }
-        });
-
-        registerForContextMenu(mListView);
-
-        return content;
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        inflater.inflate(R.menu.select_printer_activity, menu);
-
-        MenuItem searchItem = menu.findItem(R.id.action_search);
-        SearchView searchView = (SearchView) searchItem.getActionView();
-        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
-            @Override
-            public boolean onQueryTextSubmit(String query) {
-                return true;
-            }
-
-            @Override
-            public boolean onQueryTextChange(String searchString) {
-                ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString);
-                return true;
-            }
-        });
-        searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View view) {
-                if (AccessibilityManager.getInstance(getActivity()).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()) {
-                    view.announceForAccessibility(getString(
-                            R.string.print_search_box_hidden_utterance));
-                }
-            }
-        });
-
-        if (mAddPrinterServices.isEmpty()) {
-            menu.removeItem(R.id.action_add_printer);
-        }
-    }
-
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        if (view == mListView) {
-            final int position = ((AdapterContextMenuInfo) menuInfo).position;
-            PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
-
-            menu.setHeaderTitle(printer.getName());
-
-            // Add the select menu item if applicable.
-            if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
-                MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer,
-                        Menu.NONE, R.string.print_select_printer);
-                Intent intent = new Intent();
-                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
-                selectItem.setIntent(intent);
-            }
-
-            // Add the forget menu item if applicable.
-            FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
-                    getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
-            if (provider.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();
-                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
-                forgetItem.setIntent(intent);
-            }
-        }
-    }
-
-    @Override
-    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");
-                }
-            } 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);
-            } return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onResume() {
-        updateAddPrintersAdapter();
-        getActivity().invalidateOptionsMenu();
-        super.onResume();
-    }
-
-    @Override
-    public void onPause() {
-        if (mAnnounceFilterResult != null) {
-            mAnnounceFilterResult.remove();
-        }
-        super.onPause();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == R.id.action_add_printer) {
-            showAddPrinterSelectionDialog();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    private void updateAddPrintersAdapter() {
-        mAddPrinterServices.clear();
-
-        // Get all enabled print services.
-        PrintManager printManager = (PrintManager) getActivity()
-                .getSystemService(Context.PRINT_SERVICE);
-        List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
-
-        // No enabled print services - done.
-        if (enabledServices.isEmpty()) {
-            return;
-        }
-
-        // Find the services with valid add printers activities.
-        final int enabledServiceCount = enabledServices.size();
-        for (int i = 0; i < enabledServiceCount; i++) {
-            PrintServiceInfo enabledService = enabledServices.get(i);
-
-            // No add printers activity declared - done.
-            if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
-                continue;
-            }
-
-            ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
-            ComponentName addPrintersComponentName = new ComponentName(
-                    serviceInfo.packageName, enabledService.getAddPrintersActivityName());
-            Intent addPritnersIntent = new Intent()
-                .setComponent(addPrintersComponentName);
-
-            // The add printers activity is valid - add it.
-            PackageManager pm = getActivity().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())
-                                        == PackageManager.PERMISSION_GRANTED)) {
-                    mAddPrinterServices.add(enabledService);
-                }
-            }
-        }
-    }
-
-    private void showAddPrinterSelectionDialog() {
-        FragmentTransaction transaction = getFragmentManager().beginTransaction();
-        Fragment oldFragment = getFragmentManager().findFragmentByTag(
-                FRAGMRNT_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,
-                mAddPrinterServices);
-        newFragment.setArguments(arguments);
-        transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
-        transaction.commit();
-    }
-
-    public void updateEmptyView(DestinationAdapter adapter) {
-        if (mListView.getEmptyView() == null) {
-            View emptyView = getActivity().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);
-        if (adapter.getUnfilteredCount() <= 0) {
-            titleView.setText(R.string.print_searching_for_printers);
-            progressBar.setVisibility(View.VISIBLE);
-        } else {
-            titleView.setText(R.string.print_no_printers);
-            progressBar.setVisibility(View.GONE);
-        }
-    }
-
-    private void announceSearchResultIfNeeded() {
-        if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
-            if (mAnnounceFilterResult == null) {
-                mAnnounceFilterResult = new AnnounceFilterResult();
-            }
-            mAnnounceFilterResult.post();
-        }
-    }
-
-    public static class AddPrinterAlertDialogFragment extends DialogFragment {
-
-        private String mAddPrintServiceItem;
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
-                    .setTitle(R.string.choose_print_service);
-
-            final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
-                    getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
-
-            final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
-                    getActivity(), android.R.layout.simple_list_item_1);
-            final int printServiceCount = printServices.size();
-            for (int i = 0; i < printServiceCount; i++) {
-                PrintServiceInfo printService = printServices.get(i);
-                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;
-            if (!TextUtils.isEmpty(searchUri)) {
-                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
-                if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
-                    marketIntent = intent;
-                    mAddPrintServiceItem = getString(R.string.add_print_service_label);
-                    adapter.add(mAddPrintServiceItem);
-                } else {
-                    marketIntent = null;
-                }
-            } else {
-                marketIntent = null;
-            }
-
-            builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    String item = adapter.getItem(which);
-                    if (item == mAddPrintServiceItem) {
-                        try {
-                          startActivity(marketIntent);
-                      } catch (ActivityNotFoundException anfe) {
-                          Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
-                      }
-                    } else {
-                        PrintServiceInfo printService = printServices.get(which);
-                        ComponentName componentName = new ComponentName(
-                                printService.getResolveInfo().serviceInfo.packageName,
-                                printService.getAddPrintersActivityName());
-                        Intent intent = new Intent(Intent.ACTION_MAIN);
-                        intent.setComponent(componentName);
-                        try {
-                            startActivity(intent);
-                        } catch (ActivityNotFoundException anfe) {
-                            Log.w(LOG_TAG, "Couldn't start settings activity", anfe);
-                        }
-                    }
-                }
-            });
-
-            return builder.create();
-        }
-    }
-
-    private final class DestinationAdapter extends BaseAdapter
-            implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
-
-        private final Object mLock = new Object();
-
-        private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
-        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
-
-        private CharSequence mLastSearchString;
-
-        public DestinationAdapter() {
-            getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
-        }
-
-        @Override
-        public Filter getFilter() {
-            return new Filter() {
-                @Override
-                protected FilterResults performFiltering(CharSequence constraint) {
-                    synchronized (mLock) {
-                        if (TextUtils.isEmpty(constraint)) {
-                            return null;
-                        }
-                        FilterResults results = new FilterResults();
-                        List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
-                        String constraintLowerCase = constraint.toString().toLowerCase();
-                        final int printerCount = mPrinters.size();
-                        for (int i = 0; i < printerCount; i++) {
-                            PrinterInfo printer = mPrinters.get(i);
-                            if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
-                                filteredPrinters.add(printer);
-                            }
-                        }
-                        results.values = filteredPrinters;
-                        results.count = filteredPrinters.size();
-                        return results;
-                    }
-                }
-
-                @Override
-                @SuppressWarnings("unchecked")
-                protected void publishResults(CharSequence constraint, FilterResults results) {
-                    final boolean resultCountChanged;
-                    synchronized (mLock) {
-                        final int oldPrinterCount = mFilteredPrinters.size();
-                        mLastSearchString = constraint;
-                        mFilteredPrinters.clear();
-                        if (results == null) {
-                            mFilteredPrinters.addAll(mPrinters);
-                        } else {
-                            List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
-                            mFilteredPrinters.addAll(printers);
-                        }
-                        resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
-                    }
-                    if (resultCountChanged) {
-                        announceSearchResultIfNeeded();
-                    }
-                    notifyDataSetChanged();
-                }
-            };
-        }
-
-        public int getUnfilteredCount() {
-            synchronized (mLock) {
-                return mPrinters.size();
-            }
-        }
-
-        @Override
-        public int getCount() {
-            synchronized (mLock) {
-                return mFilteredPrinters.size();
-            }
-        }
-
-        @Override
-        public Object getItem(int position) {
-            synchronized (mLock) {
-                return mFilteredPrinters.get(position);
-            }
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getDropDownView(int position, View convertView,
-                ViewGroup parent) {
-            return getView(position, convertView, parent);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = getActivity().getLayoutInflater().inflate(
-                        R.layout.printer_list_item, parent, false);
-            }
-
-            convertView.setEnabled(isActionable(position));
-
-            CharSequence title = null;
-            CharSequence subtitle = null;
-            Drawable icon = null;
-
-            PrinterInfo printer = (PrinterInfo) getItem(position);
-            title = printer.getName();
-            try {
-                PackageManager pm = getActivity().getPackageManager();
-                PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
-                        .getServiceName().getPackageName(), 0);
-                subtitle = packageInfo.applicationInfo.loadLabel(pm);
-                icon = packageInfo.applicationInfo.loadIcon(pm);
-            } 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.GONE);
-            }
-
-            return convertView;
-        }
-
-        public boolean isActionable(int position) {
-            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 {
-        private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
-
-        public void post() {
-            remove();
-            mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
-        }
-
-        public void remove() {
-            mListView.removeCallbacks(this);
-        }
-
-        @Override
-        public void run() {
-            final int count = mListView.getAdapter().getCount();
-            final String text;
-            if (count <= 0) {
-                text = getString(R.string.print_no_printers);
-            } else {
-                text = getActivity().getResources().getQuantityString(
-                    R.plurals.print_search_result_count_utterance, count, count);
-            }
-            mListView.announceForAccessibility(text);
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
new file mode 100644
index 0000000..929f0fc
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -0,0 +1,391 @@
+/*
+ * 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.model;
+
+import android.app.Notification;
+import android.app.Notification.InboxStyle;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.print.IPrintManager;
+import android.print.PrintJobId;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.printspooler.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is responsible for updating the print notifications
+ * based on print job state transitions.
+ */
+final class NotificationController {
+    public static final boolean DEBUG = false;
+
+    public static final String LOG_TAG = "NotificationController";
+
+    private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
+    private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
+
+    private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
+    private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
+    private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+
+    public NotificationController(Context context) {
+        mContext = context;
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
+        List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
+
+        final int printJobCount = printJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = printJobs.get(i);
+            if (shouldNotifyForState(printJob.getState())) {
+                notifyPrintJobs.add(printJob);
+            }
+        }
+
+        updateNotification(notifyPrintJobs);
+    }
+
+    private void updateNotification(List<PrintJobInfo> printJobs) {
+        if (printJobs.size() <= 0) {
+            removeNotification();
+        } else if (printJobs.size() == 1) {
+            createSimpleNotification(printJobs.get(0));
+        } else {
+            createStackedNotification(printJobs);
+        }
+    }
+
+    private void createSimpleNotification(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_FAILED: {
+                createFailedNotification(printJob);
+            } break;
+
+            case PrintJobInfo.STATE_BLOCKED: {
+                if (!printJob.isCancelling()) {
+                    createBlockedNotification(printJob);
+                } else {
+                    createCancellingNotification(printJob);
+                }
+            } break;
+
+            default: {
+                if (!printJob.isCancelling()) {
+                    createPrintingNotification(printJob);
+                } else {
+                    createCancellingNotification(printJob);
+                }
+            } break;
+        }
+    }
+
+    private void createPrintingNotification(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(printJob.getId()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
+                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob))
+                .setContentText(printJob.getPrinterName())
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private void createFailedNotification(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(printJob.getId()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
+                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob))
+                .addAction(R.drawable.ic_restart, mContext.getString(R.string.restart),
+                        createRestartIntent(printJob.getId()))
+                .setContentText(printJob.getPrinterName())
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private void createBlockedNotification(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(printJob.getId()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
+                .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob))
+                .setContentText(printJob.getPrinterName())
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+           mNotificationManager.notify(0, builder.build());
+    }
+
+    private void createCancellingNotification(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(printJob.getId()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
+                .setContentText(printJob.getPrinterName())
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private void createStackedNotification(List<PrintJobInfo> printJobs) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(null))
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+
+        final int printJobCount = printJobs.size();
+
+        InboxStyle inboxStyle = new InboxStyle();
+        inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
+                R.plurals.composite_notification_title_template,
+                printJobCount).toString(), printJobCount));
+
+        for (int i = printJobCount - 1; i>= 0; i--) {
+            PrintJobInfo printJob = printJobs.get(i);
+            if (i == printJobCount - 1) {
+                builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
+                        computeNotificationIcon(printJob))).getBitmap());
+                builder.setSmallIcon(computeNotificationIcon(printJob));
+                builder.setContentTitle(computeNotificationTitle(printJob));
+                builder.setContentText(printJob.getPrinterName());
+            }
+            inboxStyle.addLine(computeNotificationTitle(printJob));
+        }
+
+        builder.setNumber(printJobCount);
+        builder.setStyle(inboxStyle);
+
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private String computeNotificationTitle(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_FAILED: {
+                return mContext.getString(R.string.failed_notification_title_template,
+                        printJob.getLabel());
+            }
+
+            case PrintJobInfo.STATE_BLOCKED: {
+                if (!printJob.isCancelling()) {
+                    return mContext.getString(R.string.blocked_notification_title_template,
+                            printJob.getLabel());
+                } else {
+                    return mContext.getString(
+                            R.string.cancelling_notification_title_template,
+                            printJob.getLabel());
+                }
+            }
+
+            default: {
+                if (!printJob.isCancelling()) {
+                    return mContext.getString(R.string.printing_notification_title_template,
+                            printJob.getLabel());
+                } else {
+                    return mContext.getString(
+                            R.string.cancelling_notification_title_template,
+                            printJob.getLabel());
+                }
+            }
+        }
+    }
+
+    private void removeNotification() {
+        mNotificationManager.cancel(0);
+    }
+
+    private PendingIntent createContentIntent(PrintJobId printJobId) {
+        Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
+        if (printJobId != null) {
+            intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
+            intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null));
+        }
+        return PendingIntent.getActivity(mContext, 0, intent, 0);
+    }
+
+    private PendingIntent createCancelIntent(PrintJobInfo printJob) {
+        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
+        intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
+        intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
+        intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
+        intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+    }
+
+    private PendingIntent createRestartIntent(PrintJobId printJobId) {
+        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
+        intent.setAction(INTENT_ACTION_RESTART_PRINTJOB + "_" + printJobId.flattenToString());
+        intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId);
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+    }
+
+    private static boolean shouldNotifyForState(int state) {
+        switch (state) {
+            case PrintJobInfo.STATE_QUEUED:
+            case PrintJobInfo.STATE_STARTED:
+            case PrintJobInfo.STATE_FAILED:
+            case PrintJobInfo.STATE_COMPLETED:
+            case PrintJobInfo.STATE_CANCELED:
+            case PrintJobInfo.STATE_BLOCKED: {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int computeNotificationIcon(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_FAILED:
+            case PrintJobInfo.STATE_BLOCKED: {
+                return com.android.internal.R.drawable.ic_print_error;
+            }
+            default: {
+                if (!printJob.isCancelling()) {
+                    return com.android.internal.R.drawable.ic_print;
+                } else {
+                    return R.drawable.stat_notify_cancelling;
+                }
+            }
+        }
+    }
+
+    public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
+        private static final String LOG_TAG = "NotificationBroadcastReceiver";
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
+                PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
+                String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
+                String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
+                handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
+            } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
+                PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
+                handleRestartPrintJob(context, printJobId);
+            }
+        }
+
+        private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
+                final String printJobLabel, final String printerName) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
+            }
+
+            // Call into the print manager service off the main thread since
+            // the print manager service may end up binding to the print spooler
+            // service which binding is handled on the main thread.
+            PowerManager powerManager = (PowerManager)
+                    context.getSystemService(Context.POWER_SERVICE);
+            final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    LOG_TAG);
+            wakeLock.acquire();
+
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    // We need to request the cancellation to be done by the print
+                    // manager service since it has to communicate with the managing
+                    // print service to request the cancellation. Also we need the
+                    // system service to be bound to the spooler since canceling a
+                    // print job will trigger persistence of current jobs which is
+                    // done on another thread and until it finishes the spooler has
+                    // to be kept around.
+                    try {
+                        IPrintManager printManager = IPrintManager.Stub.asInterface(
+                                ServiceManager.getService(Context.PRINT_SERVICE));
+                        printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
+                                UserHandle.myUserId());
+                    } catch (RemoteException re) {
+                        Log.i(LOG_TAG, "Error requestion print job cancellation", re);
+                    } finally {
+                        wakeLock.release();
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        }
+
+        private void handleRestartPrintJob(final Context context, final PrintJobId printJobId) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "handleRestartPrintJob() printJobId:" + printJobId);
+            }
+
+            // Call into the print manager service off the main thread since
+            // the print manager service may end up binding to the print spooler
+            // service which binding is handled on the main thread.
+            PowerManager powerManager = (PowerManager)
+                    context.getSystemService(Context.POWER_SERVICE);
+            final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    LOG_TAG);
+            wakeLock.acquire();
+
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    // We need to request the restart to be done by the print manager
+                    // service since the latter must be bound to the spooler because
+                    // restarting a print job will trigger persistence of current jobs
+                    // which is done on another thread and until it finishes the spooler has
+                    // to be kept around.
+                    try {
+                        IPrintManager printManager = IPrintManager.Stub.asInterface(
+                                ServiceManager.getService(Context.PRINT_SERVICE));
+                        printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY,
+                                UserHandle.myUserId());
+                    } catch (RemoteException re) {
+                        Log.i(LOG_TAG, "Error requestion print job restart", re);
+                    } finally {
+                        wakeLock.release();
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        }
+    }
+}
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/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
new file mode 100644
index 0000000..045a2f9
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -0,0 +1,1300 @@
+/*
+ * 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.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;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.IPrintSpooler;
+import android.print.IPrintSpoolerCallbacks;
+import android.print.IPrintSpoolerClient;
+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.PrintDocumentInfo;
+import android.print.PrintJobId;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.R;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service for exposing some of the {@link PrintSpooler} functionality to
+ * another process.
+ */
+public final class PrintSpoolerService extends Service {
+
+    private static final String LOG_TAG = "PrintSpoolerService";
+
+    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+
+    private static final boolean DEBUG_PERSISTENCE = false;
+
+    private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
+
+    private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
+
+    private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
+
+    private static final String PRINT_FILE_EXTENSION = "pdf";
+
+    private static final Object sLock = new Object();
+
+    private final Object mLock = new Object();
+
+    private final List<PrintJobInfo> mPrintJobs = new ArrayList<>();
+
+    private static PrintSpoolerService sInstance;
+
+    private IPrintSpoolerClient mClient;
+
+    private HandlerCaller mHandlerCaller;
+
+    private PersistenceManager mPersistanceManager;
+
+    private NotificationController mNotificationController;
+
+    public static PrintSpoolerService peekInstance() {
+        synchronized (sLock) {
+            return sInstance;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandlerCaller = new HandlerCaller(this, getMainLooper(),
+                new HandlerCallerCallback(), false);
+
+        mPersistanceManager = new PersistenceManager();
+        mNotificationController = new NotificationController(PrintSpoolerService.this);
+
+        synchronized (mLock) {
+            mPersistanceManager.readStateLocked();
+            handleReadPrintJobsLocked();
+        }
+
+        synchronized (sLock) {
+            sInstance = this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new PrintSpooler();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            String prefix = (args.length > 0) ? args[0] : "";
+            String tab = "  ";
+
+            pw.append(prefix).append("print jobs:").println();
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                pw.append(prefix).append(tab).append(printJob.toString());
+                pw.println();
+            }
+
+            pw.append(prefix).append("print job files:").println();
+            File[] files = getFilesDir().listFiles();
+            if (files != null) {
+                final int fileCount = files.length;
+                for (int i = 0; i < fileCount; i++) {
+                    File file = files[i];
+                    if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                        pw.append(prefix).append(tab).append(file.getName()).println();
+                    }
+                }
+            }
+        }
+    }
+
+    private void sendOnPrintJobQueued(PrintJobInfo printJob) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private void sendOnAllPrintJobsHandled() {
+        Message message = mHandlerCaller.obtainMessage(
+                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private final class HandlerCallerCallback implements HandlerCaller.Callback {
+        public static final int MSG_SET_CLIENT = 1;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
+        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
+        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
+        public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
+
+        @Override
+        public void executeMessage(Message message) {
+            switch (message.what) {
+                case MSG_SET_CLIENT: {
+                    synchronized (mLock) {
+                        mClient = (IPrintSpoolerClient) message.obj;
+                        if (mClient != null) {
+                            Message msg = mHandlerCaller.obtainMessage(
+                                    HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
+                            mHandlerCaller.sendMessageDelayed(msg,
+                                    CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
+                        }
+                    }
+                } break;
+
+                case MSG_ON_PRINT_JOB_QUEUED: {
+                    PrintJobInfo printJob = (PrintJobInfo) message.obj;
+                    if (mClient != null) {
+                        try {
+                            mClient.onPrintJobQueued(printJob);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
+                    ComponentName service = (ComponentName) message.obj;
+                    if (mClient != null) {
+                        try {
+                            mClient.onAllPrintJobsForServiceHandled(service);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for all print jobs per service"
+                                    + " handled.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
+                    if (mClient != null) {
+                        try {
+                            mClient.onAllPrintJobsHandled();
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
+                    checkAllPrintJobsHandled();
+                } break;
+
+                case MSG_ON_PRINT_JOB_STATE_CHANGED: {
+                    if (mClient != null) {
+                        PrintJobInfo printJob = (PrintJobInfo) message.obj;
+                        try {
+                            mClient.onPrintJobStateChanged(printJob);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error notify for print job state change.", re);
+                        }
+                    }
+                } break;
+            }
+        }
+    }
+
+    public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
+            int state, int appId) {
+        List<PrintJobInfo> foundPrintJobs = null;
+        synchronized (mLock) {
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                PrinterId printerId = printJob.getPrinterId();
+                final boolean sameComponent = (componentName == null
+                        || (printerId != null
+                        && componentName.equals(printerId.getServiceName())));
+                final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+                        || printJob.getAppId() == appId;
+                final boolean sameState = (state == printJob.getState())
+                        || (state == PrintJobInfo.STATE_ANY)
+                        || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
+                            && isStateVisibleToUser(printJob.getState()))
+                        || (state == PrintJobInfo.STATE_ANY_ACTIVE
+                            && isActiveState(printJob.getState()))
+                        || (state == PrintJobInfo.STATE_ANY_SCHEDULED
+                            && isScheduledState(printJob.getState()));
+                if (sameComponent && sameAppId && sameState) {
+                    if (foundPrintJobs == null) {
+                        foundPrintJobs = new ArrayList<>();
+                    }
+                    foundPrintJobs.add(printJob);
+                }
+            }
+        }
+        return foundPrintJobs;
+    }
+
+    private boolean isStateVisibleToUser(int state) {
+        return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
+                || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
+                || state == PrintJobInfo.STATE_BLOCKED));
+    }
+
+    public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
+        synchronized (mLock) {
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                if (printJob.getId().equals(printJobId)
+                        && (appId == PrintManager.APP_ID_ANY
+                        || appId == printJob.getAppId())) {
+                    return printJob;
+                }
+            }
+            return null;
+        }
+    }
+
+    public void createPrintJob(PrintJobInfo printJob) {
+        synchronized (mLock) {
+            addPrintJobLocked(printJob);
+            setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
+
+            Message message = mHandlerCaller.obtainMessageO(
+                    HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+                    printJob);
+            mHandlerCaller.executeOrSendMessage(message);
+        }
+    }
+
+    private void handleReadPrintJobsLocked() {
+        // Make a map with the files for a print job since we may have
+        // to delete some. One example of getting orphan files if the
+        // spooler crashes while constructing a print job. We do not
+        // persist partially populated print jobs under construction to
+        // avoid special handling for various attributes missing.
+        ArrayMap<PrintJobId, File> fileForJobMap = null;
+        File[] files = getFilesDir().listFiles();
+        if (files != null) {
+            final int fileCount = files.length;
+            for (int i = 0; i < fileCount; i++) {
+                File file = files[i];
+                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                    if (fileForJobMap == null) {
+                        fileForJobMap = new ArrayMap<PrintJobId, File>();
+                    }
+                    String printJobIdString = file.getName().substring(
+                            PRINT_JOB_FILE_PREFIX.length(),
+                            file.getName().indexOf('.'));
+                    PrintJobId printJobId = PrintJobId.unflattenFromString(
+                            printJobIdString);
+                    fileForJobMap.put(printJobId, file);
+                }
+            }
+        }
+
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+
+            // We want to have only the orphan files at the end.
+            if (fileForJobMap != null) {
+                fileForJobMap.remove(printJob.getId());
+            }
+
+            switch (printJob.getState()) {
+                case PrintJobInfo.STATE_QUEUED:
+                case PrintJobInfo.STATE_STARTED:
+                case PrintJobInfo.STATE_BLOCKED: {
+                    // We have a print job that was queued or started or blocked in
+                    // the past but the device battery died or a crash occurred. In
+                    // this case we assume the print job failed and let the user
+                    // decide whether to restart the job or just cancel it.
+                    setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
+                            getString(R.string.no_connection_to_printer));
+                } break;
+            }
+        }
+
+        if (!mPrintJobs.isEmpty()) {
+            // Update the notification.
+            mNotificationController.onUpdateNotifications(mPrintJobs);
+        }
+
+        // Delete the orphan files.
+        if (fileForJobMap != null) {
+            final int orphanFileCount = fileForJobMap.size();
+            for (int i = 0; i < orphanFileCount; i++) {
+                File file = fileForJobMap.valueAt(i);
+                file.delete();
+            }
+        }
+    }
+
+    public void checkAllPrintJobsHandled() {
+        synchronized (mLock) {
+            if (!hasActivePrintJobsLocked()) {
+                notifyOnAllPrintJobsHandled();
+            }
+        }
+    }
+
+    public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
+        final PrintJobInfo printJob;
+        synchronized (mLock) {
+            printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+        }
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                FileInputStream in = null;
+                FileOutputStream out = null;
+                try {
+                    if (printJob != null) {
+                        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
+                        in = new FileInputStream(file);
+                        out = new FileOutputStream(fd.getFileDescriptor());
+                    }
+                    final byte[] buffer = new byte[8192];
+                    while (true) {
+                        final int readByteCount = in.read(buffer);
+                        if (readByteCount < 0) {
+                            return null;
+                        }
+                        out.write(buffer, 0, readByteCount);
+                    }
+                } catch (FileNotFoundException fnfe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                    IoUtils.closeQuietly(out);
+                    IoUtils.closeQuietly(fd);
+                }
+                Log.i(LOG_TAG, "[END WRITE]");
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+
+    public static File generateFileForPrintJob(Context context, PrintJobId printJobId) {
+        return new File(context.getFilesDir(), PRINT_JOB_FILE_PREFIX
+                + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
+    }
+
+    private void addPrintJobLocked(PrintJobInfo printJob) {
+        mPrintJobs.add(printJob);
+        if (DEBUG_PRINT_JOB_LIFECYCLE) {
+            Slog.i(LOG_TAG, "[ADD] " + printJob);
+        }
+    }
+
+    private void removeObsoletePrintJobs() {
+        synchronized (mLock) {
+            boolean persistState = false;
+            final int printJobCount = mPrintJobs.size();
+            for (int i = printJobCount - 1; i >= 0; i--) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                if (isObsoleteState(printJob.getState())) {
+                    mPrintJobs.remove(i);
+                    if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                        Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
+                    }
+                    removePrintJobFileLocked(printJob.getId());
+                    persistState = true;
+                }
+            }
+            if (persistState) {
+                mPersistanceManager.writeStateLocked();
+            }
+        }
+    }
+
+    private void removePrintJobFileLocked(PrintJobId printJobId) {
+        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
+        if (file.exists()) {
+            file.delete();
+            if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
+            }
+        }
+    }
+
+    public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
+        boolean success = false;
+
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                final int oldState = printJob.getState();
+                if (oldState == state) {
+                    return false;
+                }
+
+                success = true;
+
+                printJob.setState(state);
+                printJob.setStateReason(error);
+                printJob.setCancelling(false);
+
+                if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                    Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+                }
+
+                switch (state) {
+                    case PrintJobInfo.STATE_COMPLETED:
+                    case PrintJobInfo.STATE_CANCELED:
+                        mPrintJobs.remove(printJob);
+                        removePrintJobFileLocked(printJob.getId());
+                        // $fall-through$
+
+                    case PrintJobInfo.STATE_FAILED: {
+                        PrinterId printerId = printJob.getPrinterId();
+                        if (printerId != null) {
+                            ComponentName service = printerId.getServiceName();
+                            if (!hasActivePrintJobsForServiceLocked(service)) {
+                                sendOnAllPrintJobsForServiceHandled(service);
+                            }
+                        }
+                    } break;
+
+                    case PrintJobInfo.STATE_QUEUED: {
+                        sendOnPrintJobQueued(new PrintJobInfo(printJob));
+                    }  break;
+                }
+
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+
+                if (!hasActivePrintJobsLocked()) {
+                    notifyOnAllPrintJobsHandled();
+                }
+
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+                        printJob);
+                mHandlerCaller.executeOrSendMessage(message);
+
+                mNotificationController.onUpdateNotifications(mPrintJobs);
+            }
+        }
+
+        return success;
+    }
+
+    public boolean hasActivePrintJobsLocked() {
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+            if (isActiveState(printJob.getState())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+            if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null
+                    && printJob.getPrinterId().getServiceName().equals(service)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isObsoleteState(int printJobState) {
+        return (isTerminalState(printJobState)
+                || printJobState == PrintJobInfo.STATE_QUEUED);
+    }
+
+    private boolean isScheduledState(int printJobState) {
+        return printJobState == PrintJobInfo.STATE_QUEUED
+                || printJobState == PrintJobInfo.STATE_STARTED
+                || printJobState == PrintJobInfo.STATE_BLOCKED;
+    }
+
+    private boolean isActiveState(int printJobState) {
+        return printJobState == PrintJobInfo.STATE_CREATED
+                || printJobState == PrintJobInfo.STATE_QUEUED
+                || printJobState == PrintJobInfo.STATE_STARTED
+                || printJobState == PrintJobInfo.STATE_BLOCKED;
+    }
+
+    private boolean isTerminalState(int printJobState) {
+        return printJobState == PrintJobInfo.STATE_COMPLETED
+                || printJobState == PrintJobInfo.STATE_CANCELED;
+    }
+
+    public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                String printJobTag = printJob.getTag();
+                if (printJobTag == null) {
+                    if (tag == null) {
+                        return false;
+                    }
+                } else if (printJobTag.equals(tag)) {
+                    return false;
+                }
+                printJob.setTag(tag);
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setCancelling(cancelling);
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+                mNotificationController.onUpdateNotifications(mPrintJobs);
+
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+                        printJob);
+                mHandlerCaller.executeOrSendMessage(message);
+            }
+        }
+    }
+
+    public void updatePrintJobUserConfigurableOptionsNoPersistence(PrintJobInfo printJob) {
+        synchronized (mLock) {
+            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;
+                }
+            }
+            throw new IllegalArgumentException("No print job with id:" + printJob.getId());
+        }
+    }
+
+    private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+        return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
+    }
+
+    private void notifyOnAllPrintJobsHandled() {
+        // This has to run on the tread that is persisting the current state
+        // since this call may result in the system unbinding from the spooler
+        // and as a result the spooler process may get killed before the write
+        // completes.
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                sendOnAllPrintJobsHandled();
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+    }
+
+    private final class PersistenceManager {
+        private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
+
+        private static final String TAG_SPOOLER = "spooler";
+        private static final String TAG_JOB = "job";
+
+        private static final String TAG_PRINTER_ID = "printerId";
+        private static final String TAG_PAGE_RANGE = "pageRange";
+        private static final String TAG_ATTRIBUTES = "attributes";
+        private static final String TAG_DOCUMENT_INFO = "documentInfo";
+
+        private static final String ATTR_ID = "id";
+        private static final String ATTR_LABEL = "label";
+        private static final String ATTR_LABEL_RES_ID = "labelResId";
+        private static final String ATTR_PACKAGE_NAME = "packageName";
+        private static final String ATTR_STATE = "state";
+        private static final String ATTR_APP_ID = "appId";
+        private static final String ATTR_TAG = "tag";
+        private static final String ATTR_CREATION_TIME = "creationTime";
+        private static final String ATTR_COPIES = "copies";
+        private static final String ATTR_PRINTER_NAME = "printerName";
+        private static final String ATTR_STATE_REASON = "stateReason";
+        private static final String ATTR_CANCELLING = "cancelling";
+
+        private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
+        private static final String TAG_ADVANCED_OPTION = "advancedOption";
+        private static final String ATTR_KEY = "key";
+        private static final String ATTR_TYPE = "type";
+        private static final String ATTR_VALUE = "value";
+        private static final String TYPE_STRING = "string";
+        private static final String TYPE_INT = "int";
+
+        private static final String TAG_MEDIA_SIZE = "mediaSize";
+        private static final String TAG_RESOLUTION = "resolution";
+        private static final String TAG_MARGINS = "margins";
+
+        private static final String ATTR_COLOR_MODE = "colorMode";
+
+        private static final String ATTR_LOCAL_ID = "localId";
+        private static final String ATTR_SERVICE_NAME = "serviceName";
+
+        private static final String ATTR_WIDTH_MILS = "widthMils";
+        private static final String ATTR_HEIGHT_MILS = "heightMils";
+
+        private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
+        private static final String ATTR_VERTICAL_DPI = "verticalDpi";
+
+        private static final String ATTR_LEFT_MILS = "leftMils";
+        private static final String ATTR_TOP_MILS = "topMils";
+        private static final String ATTR_RIGHT_MILS = "rightMils";
+        private static final String ATTR_BOTTOM_MILS = "bottomMils";
+
+        private static final String ATTR_START = "start";
+        private static final String ATTR_END = "end";
+
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_PAGE_COUNT = "pageCount";
+        private static final String ATTR_CONTENT_TYPE = "contentType";
+        private static final String ATTR_DATA_SIZE = "dataSize";
+
+        private final AtomicFile mStatePersistFile;
+
+        private boolean mWriteStateScheduled;
+
+        private PersistenceManager() {
+            mStatePersistFile = new AtomicFile(new File(getFilesDir(),
+                    PERSIST_FILE_NAME));
+        }
+
+        public void writeStateLocked() {
+            if (!PERSISTNECE_MANAGER_ENABLED) {
+                return;
+            }
+            if (mWriteStateScheduled) {
+                return;
+            }
+            mWriteStateScheduled = true;
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (mLock) {
+                        mWriteStateScheduled = false;
+                        doWriteStateLocked();
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        }
+
+        private void doWriteStateLocked() {
+            if (DEBUG_PERSISTENCE) {
+                Log.i(LOG_TAG, "[PERSIST START]");
+            }
+            FileOutputStream out = null;
+            try {
+                out = mStatePersistFile.startWrite();
+
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(out, "utf-8");
+                serializer.startDocument(null, true);
+                serializer.startTag(null, TAG_SPOOLER);
+
+                List<PrintJobInfo> printJobs = mPrintJobs;
+
+                final int printJobCount = printJobs.size();
+                for (int j = 0; j < printJobCount; j++) {
+                    PrintJobInfo printJob = printJobs.get(j);
+
+                    if (!shouldPersistPrintJob(printJob)) {
+                        continue;
+                    }
+
+                    serializer.startTag(null, TAG_JOB);
+
+                    serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
+                    serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
+                    serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
+                    serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
+                    String tag = printJob.getTag();
+                    if (tag != null) {
+                        serializer.attribute(null, ATTR_TAG, tag);
+                    }
+                    serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
+                            printJob.getCreationTime()));
+                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
+                    String printerName = printJob.getPrinterName();
+                    if (!TextUtils.isEmpty(printerName)) {
+                        serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
+                    }
+                    String stateReason = printJob.getStateReason();
+                    if (!TextUtils.isEmpty(stateReason)) {
+                        serializer.attribute(null, ATTR_STATE_REASON, stateReason);
+                    }
+                    serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
+                            printJob.isCancelling()));
+
+                    PrinterId printerId = printJob.getPrinterId();
+                    if (printerId != null) {
+                        serializer.startTag(null, TAG_PRINTER_ID);
+                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+                                .flattenToString());
+                        serializer.endTag(null, TAG_PRINTER_ID);
+                    }
+
+                    PageRange[] pages = printJob.getPages();
+                    if (pages != null) {
+                        for (int i = 0; i < pages.length; i++) {
+                            serializer.startTag(null, TAG_PAGE_RANGE);
+                            serializer.attribute(null, ATTR_START, String.valueOf(
+                                    pages[i].getStart()));
+                            serializer.attribute(null, ATTR_END, String.valueOf(
+                                    pages[i].getEnd()));
+                            serializer.endTag(null, TAG_PAGE_RANGE);
+                        }
+                    }
+
+                    PrintAttributes attributes = printJob.getAttributes();
+                    if (attributes != null) {
+                        serializer.startTag(null, TAG_ATTRIBUTES);
+
+                        final int colorMode = attributes.getColorMode();
+                        serializer.attribute(null, ATTR_COLOR_MODE,
+                                String.valueOf(colorMode));
+
+                        MediaSize mediaSize = attributes.getMediaSize();
+                        if (mediaSize != null) {
+                            serializer.startTag(null, TAG_MEDIA_SIZE);
+                            serializer.attribute(null, ATTR_ID, mediaSize.getId());
+                            serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
+                                    mediaSize.getWidthMils()));
+                            serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
+                                    mediaSize.getHeightMils()));
+                            // We prefer to store only the package name and
+                            // resource id and fallback to the label.
+                            if (!TextUtils.isEmpty(mediaSize.mPackageName)
+                                    && mediaSize.mLabelResId > 0) {
+                                serializer.attribute(null, ATTR_PACKAGE_NAME,
+                                        mediaSize.mPackageName);
+                                serializer.attribute(null, ATTR_LABEL_RES_ID,
+                                        String.valueOf(mediaSize.mLabelResId));
+                            } else {
+                                serializer.attribute(null, ATTR_LABEL,
+                                        mediaSize.getLabel(getPackageManager()));
+                            }
+                            serializer.endTag(null, TAG_MEDIA_SIZE);
+                        }
+
+                        Resolution resolution = attributes.getResolution();
+                        if (resolution != null) {
+                            serializer.startTag(null, TAG_RESOLUTION);
+                            serializer.attribute(null, ATTR_ID, resolution.getId());
+                            serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
+                                    resolution.getHorizontalDpi()));
+                            serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
+                                    resolution.getVerticalDpi()));
+                            serializer.attribute(null, ATTR_LABEL,
+                                    resolution.getLabel());
+                            serializer.endTag(null, TAG_RESOLUTION);
+                        }
+
+                        Margins margins = attributes.getMinMargins();
+                        if (margins != null) {
+                            serializer.startTag(null, TAG_MARGINS);
+                            serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
+                                    margins.getLeftMils()));
+                            serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
+                                    margins.getTopMils()));
+                            serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
+                                    margins.getRightMils()));
+                            serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
+                                    margins.getBottomMils()));
+                            serializer.endTag(null, TAG_MARGINS);
+                        }
+
+                        serializer.endTag(null, TAG_ATTRIBUTES);
+                    }
+
+                    PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
+                    if (documentInfo != null) {
+                        serializer.startTag(null, TAG_DOCUMENT_INFO);
+                        serializer.attribute(null, ATTR_NAME, documentInfo.getName());
+                        serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
+                                documentInfo.getContentType()));
+                        serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
+                                documentInfo.getPageCount()));
+                        serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
+                                documentInfo.getDataSize()));
+                        serializer.endTag(null, TAG_DOCUMENT_INFO);
+                    }
+
+                    Bundle advancedOptions = printJob.getAdvancedOptions();
+                    if (advancedOptions != null) {
+                        serializer.startTag(null, TAG_ADVANCED_OPTIONS);
+                        for (String key : advancedOptions.keySet()) {
+                            Object value = advancedOptions.get(key);
+                            if (value instanceof String) {
+                                String stringValue = (String) value;
+                                serializer.startTag(null, TAG_ADVANCED_OPTION);
+                                serializer.attribute(null, ATTR_KEY, key);
+                                serializer.attribute(null, ATTR_TYPE, TYPE_STRING);
+                                serializer.attribute(null, ATTR_VALUE, stringValue);
+                                serializer.endTag(null, TAG_ADVANCED_OPTION);
+                            } else if (value instanceof Integer) {
+                                String intValue = Integer.toString((Integer) value);
+                                serializer.startTag(null, TAG_ADVANCED_OPTION);
+                                serializer.attribute(null, ATTR_KEY, key);
+                                serializer.attribute(null, ATTR_TYPE, TYPE_INT);
+                                serializer.attribute(null, ATTR_VALUE, intValue);
+                                serializer.endTag(null, TAG_ADVANCED_OPTION);
+                            }
+                        }
+                        serializer.endTag(null, TAG_ADVANCED_OPTIONS);
+                    }
+
+                    serializer.endTag(null, TAG_JOB);
+
+                    if (DEBUG_PERSISTENCE) {
+                        Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+                    }
+                }
+
+                serializer.endTag(null, TAG_SPOOLER);
+                serializer.endDocument();
+                mStatePersistFile.finishWrite(out);
+                if (DEBUG_PERSISTENCE) {
+                    Log.i(LOG_TAG, "[PERSIST END]");
+                }
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+                mStatePersistFile.failWrite(out);
+            } finally {
+                IoUtils.closeQuietly(out);
+            }
+        }
+
+        public void readStateLocked() {
+            if (!PERSISTNECE_MANAGER_ENABLED) {
+                return;
+            }
+            FileInputStream in = null;
+            try {
+                in = mStatePersistFile.openRead();
+            } catch (FileNotFoundException e) {
+                Log.i(LOG_TAG, "No existing print spooler state.");
+                return;
+            }
+            try {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(in, null);
+                parseState(parser);
+            } catch (IllegalStateException ise) {
+                Slog.w(LOG_TAG, "Failed parsing ", ise);
+            } catch (NullPointerException npe) {
+                Slog.w(LOG_TAG, "Failed parsing ", npe);
+            } catch (NumberFormatException nfe) {
+                Slog.w(LOG_TAG, "Failed parsing ", nfe);
+            } catch (XmlPullParserException xppe) {
+                Slog.w(LOG_TAG, "Failed parsing ", xppe);
+            } catch (IOException ioe) {
+                Slog.w(LOG_TAG, "Failed parsing ", ioe);
+            } catch (IndexOutOfBoundsException iobe) {
+                Slog.w(LOG_TAG, "Failed parsing ", iobe);
+            } finally {
+                IoUtils.closeQuietly(in);
+            }
+        }
+
+        private void parseState(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            parser.next();
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
+            parser.next();
+
+            while (parsePrintJob(parser)) {
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
+        }
+
+        private boolean parsePrintJob(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            skipEmptyTextTags(parser);
+            if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
+                return false;
+            }
+
+            PrintJobInfo printJob = new PrintJobInfo();
+
+            PrintJobId printJobId = PrintJobId.unflattenFromString(
+                    parser.getAttributeValue(null, ATTR_ID));
+            printJob.setId(printJobId);
+            String label = parser.getAttributeValue(null, ATTR_LABEL);
+            printJob.setLabel(label);
+            final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
+            printJob.setState(state);
+            final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
+            printJob.setAppId(appId);
+            String tag = parser.getAttributeValue(null, ATTR_TAG);
+            printJob.setTag(tag);
+            String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
+            printJob.setCreationTime(Long.parseLong(creationTime));
+            String copies = parser.getAttributeValue(null, ATTR_COPIES);
+            printJob.setCopies(Integer.parseInt(copies));
+            String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
+            printJob.setPrinterName(printerName);
+            String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
+            printJob.setStateReason(stateReason);
+            String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
+            printJob.setCancelling(!TextUtils.isEmpty(cancelling)
+                    ? Boolean.parseBoolean(cancelling) : false);
+
+            parser.next();
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
+                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+                        null, ATTR_SERVICE_NAME));
+                printJob.setPrinterId(new PrinterId(service, localId));
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            List<PageRange> pageRanges = null;
+            while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
+                final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
+                final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
+                PageRange pageRange = new PageRange(start, end);
+                if (pageRanges == null) {
+                    pageRanges = new ArrayList<PageRange>();
+                }
+                pageRanges.add(pageRange);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
+                parser.next();
+                skipEmptyTextTags(parser);
+            }
+            if (pageRanges != null) {
+                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+                pageRanges.toArray(pageRangesArray);
+                printJob.setPages(pageRangesArray);
+            }
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
+
+                PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+                String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
+                builder.setColorMode(Integer.parseInt(colorMode));
+
+                parser.next();
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_WIDTH_MILS));
+                    final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_HEIGHT_MILS));
+                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                    String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
+                    final int labelResId = (labelResIdString != null)
+                            ? Integer.parseInt(labelResIdString) : 0;
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    MediaSize mediaSize = new MediaSize(id, label, packageName,
+                                widthMils, heightMils, labelResId);
+                    builder.setMediaSize(mediaSize);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_HORIZONTAL_DPI));
+                    final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_VERTICAL_DPI));
+                    Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
+                    builder.setResolution(resolution);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
+                    final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_LEFT_MILS));
+                    final int topMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_TOP_MILS));
+                    final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_RIGHT_MILS));
+                    final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_BOTTOM_MILS));
+                    Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
+                    builder.setMinMargins(margins);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
+                    parser.next();
+                }
+
+                printJob.setAttributes(builder.build());
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_PAGE_COUNT));
+                final int contentType = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_CONTENT_TYPE));
+                final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_DATA_SIZE));
+                PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
+                        .setPageCount(pageCount)
+                        .setContentType(contentType).build();
+                printJob.setDocumentInfo(info);
+                info.setDataSize(dataSize);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTIONS)) {
+                parser.next();
+                skipEmptyTextTags(parser);
+                Bundle advancedOptions = new Bundle();
+                while (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTION)) {
+                    String key = parser.getAttributeValue(null, ATTR_KEY);
+                    String value = parser.getAttributeValue(null, ATTR_VALUE);
+                    String type = parser.getAttributeValue(null, ATTR_TYPE);
+                    if (TYPE_STRING.equals(type)) {
+                        advancedOptions.putString(key, value);
+                    } else if (TYPE_INT.equals(type)) {
+                        advancedOptions.putInt(key, Integer.valueOf(value));
+                    }
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTION);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                }
+                printJob.setAdvancedOptions(advancedOptions);
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTIONS);
+                parser.next();
+            }
+
+            mPrintJobs.add(printJob);
+
+            if (DEBUG_PERSISTENCE) {
+                Log.i(LOG_TAG, "[RESTORED] " + printJob);
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_JOB);
+
+            return true;
+        }
+
+        private void expect(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (!accept(parser, type, tag)) {
+                throw new XmlPullParserException("Exepected event: " + type
+                        + " and tag: " + tag + " but got event: " + parser.getEventType()
+                        + " and tag:" + parser.getName());
+            }
+        }
+
+        private void skipEmptyTextTags(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            while (accept(parser, XmlPullParser.TEXT, null)
+                    && "\n".equals(parser.getText())) {
+                parser.next();
+            }
+        }
+
+        private boolean accept(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (parser.getEventType() != type) {
+                return false;
+            }
+            if (tag != null) {
+                if (!tag.equals(parser.getName())) {
+                    return false;
+                }
+            } else if (parser.getName() != null) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    public final class PrintSpooler extends IPrintSpooler.Stub {
+        @Override
+        public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
+                ComponentName componentName, int state, int appId, int sequence)
+                throws RemoteException {
+            List<PrintJobInfo> printJobs = null;
+            try {
+                printJobs = PrintSpoolerService.this.getPrintJobInfos(
+                        componentName, state, appId);
+            } finally {
+                callback.onGetPrintJobInfosResult(printJobs, sequence);
+            }
+        }
+
+        @Override
+        public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
+                int appId, int sequence) throws RemoteException {
+            PrintJobInfo printJob = null;
+            try {
+                printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
+            } finally {
+                callback.onGetPrintJobInfoResult(printJob, sequence);
+            }
+        }
+
+        @Override
+        public void createPrintJob(PrintJobInfo printJob) {
+            PrintSpoolerService.this.createPrintJob(printJob);
+        }
+
+        @Override
+        public void setPrintJobState(PrintJobId printJobId, int state, String error,
+                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
+            boolean success = false;
+            try {
+                success = PrintSpoolerService.this.setPrintJobState(
+                        printJobId, state, error);
+            } finally {
+                callback.onSetPrintJobStateResult(success, sequece);
+            }
+        }
+
+        @Override
+        public void setPrintJobTag(PrintJobId printJobId, String tag,
+                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
+            boolean success = false;
+            try {
+                success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
+            } finally {
+                callback.onSetPrintJobTagResult(success, sequece);
+            }
+        }
+
+        @Override
+        public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
+            PrintSpoolerService.this.writePrintJobData(fd, printJobId);
+        }
+
+        @Override
+        public void setClient(IPrintSpoolerClient client) {
+            Message message = mHandlerCaller.obtainMessageO(
+                    HandlerCallerCallback.MSG_SET_CLIENT, client);
+            mHandlerCaller.executeOrSendMessage(message);
+        }
+
+        @Override
+        public void removeObsoletePrintJobs() {
+            PrintSpoolerService.this.removeObsoletePrintJobs();
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+            PrintSpoolerService.this.dump(fd, writer, args);
+        }
+
+        @Override
+        public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+            PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
+        }
+
+        public PrintSpoolerService getService() {
+            return PrintSpoolerService.this;
+        }
+    }
+}
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/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
new file mode 100644
index 0000000..d802cd8
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -0,0 +1,710 @@
+/*
+ * 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.ui;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Loader;
+import android.content.pm.ServiceInfo;
+import android.os.AsyncTask;
+import android.print.PrintManager;
+import android.print.PrinterDiscoverySession;
+import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+
+/**
+ * This class is responsible for loading printers by doing discovery
+ * and merging the discovered printers with the previously used ones.
+ */
+public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+    private static final String LOG_TAG = "FusedPrintersProvider";
+
+    private static final boolean DEBUG = false;
+
+    private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
+    private static final int MAX_HISTORY_LENGTH = 50;
+
+    private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
+
+    private final List<PrinterInfo> mPrinters =
+            new ArrayList<PrinterInfo>();
+
+    private final List<PrinterInfo> mFavoritePrinters =
+            new ArrayList<PrinterInfo>();
+
+    private final PersistenceManager mPersistenceManager;
+
+    private PrinterDiscoverySession mDiscoverySession;
+
+    private PrinterId mTrackedPrinter;
+
+    private boolean mPrintersUpdatedBefore;
+
+    public FusedPrintersProvider(Context context) {
+        super(context);
+        mPersistenceManager = new PersistenceManager(context);
+    }
+
+    public void addHistoricalPrinter(PrinterInfo printer) {
+        mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
+    }
+
+    private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
+            ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
+        List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+
+        // Add the updated favorite printers.
+        final int favoritePrinterCount = favoritePrinters.size();
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            PrinterInfo favoritePrinter = favoritePrinters.valueAt(i);
+            PrinterInfo updatedPrinter = discoveredPrinters.remove(
+                    favoritePrinter.getId());
+            if (updatedPrinter != null) {
+                printers.add(updatedPrinter);
+            } else {
+                printers.add(favoritePrinter);
+            }
+        }
+
+        // Add other updated printers.
+        final int printerCount = mPrinters.size();
+        for (int i = 0; i < printerCount; i++) {
+            PrinterInfo printer = mPrinters.get(i);
+            PrinterInfo updatedPrinter = discoveredPrinters.remove(
+                    printer.getId());
+            if (updatedPrinter != null) {
+                printers.add(updatedPrinter);
+            }
+        }
+
+        // Add the new printers, i.e. what is left.
+        printers.addAll(discoveredPrinters.values());
+
+        // Update the list of printers.
+        mPrinters.clear();
+        mPrinters.addAll(printers);
+
+        if (isStarted()) {
+            // If stated deliver the new printers.
+            deliverResult(printers);
+        } else {
+            // Otherwise, take a note for the change.
+            onContentChanged();
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onStartLoading() " + FusedPrintersProvider.this.hashCode());
+        }
+        // The contract is that if we already have a valid,
+        // result the we have to deliver it immediately.
+        if (!mPrinters.isEmpty()) {
+            deliverResult(new ArrayList<PrinterInfo>(mPrinters));
+        }
+        // Always load the data to ensure discovery period is
+        // started and to make sure obsolete printers are updated.
+        onForceLoad();
+    }
+
+    @Override
+    protected void onStopLoading() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode());
+        }
+        onCancelLoad();
+    }
+
+    @Override
+    protected void onForceLoad() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onForceLoad() " + FusedPrintersProvider.this.hashCode());
+        }
+        loadInternal();
+    }
+
+    private void loadInternal() {
+        if (mDiscoverySession == null) {
+            PrintManager printManager = (PrintManager) getContext()
+                    .getSystemService(Context.PRINT_SERVICE);
+            mDiscoverySession = printManager.createPrinterDiscoverySession();
+            mPersistenceManager.readPrinterHistory();
+        } else if (mPersistenceManager.isHistoryChanged()) {
+            mPersistenceManager.readPrinterHistory();
+        }
+        if (mPersistenceManager.isReadHistoryCompleted()
+                && !mDiscoverySession.isPrinterDiscoveryStarted()) {
+            mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
+                @Override
+                public void onPrintersChanged() {
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "onPrintersChanged() count:"
+                                + mDiscoverySession.getPrinters().size()
+                                + " " + FusedPrintersProvider.this.hashCode());
+                    }
+                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
+                }
+            });
+            final int favoriteCount = mFavoritePrinters.size();
+            List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+            for (int i = 0; i < favoriteCount; i++) {
+                printerIds.add(mFavoritePrinters.get(i).getId());
+            }
+            mDiscoverySession.startPrinterDiscovery(printerIds);
+            List<PrinterInfo> printers = mDiscoverySession.getPrinters();
+            if (!printers.isEmpty()) {
+                updatePrinters(printers, mFavoritePrinters);
+            }
+        }
+    }
+
+    private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {
+        if (mPrintersUpdatedBefore && mPrinters.equals(printers)
+                && mFavoritePrinters.equals(favoritePrinters)) {
+            return;
+        }
+
+        mPrintersUpdatedBefore = true;
+
+        ArrayMap<PrinterId, PrinterInfo> printersMap =
+                new ArrayMap<PrinterId, PrinterInfo>();
+        final int printerCount = printers.size();
+        for (int i = 0; i < printerCount; i++) {
+            PrinterInfo printer = printers.get(i);
+            printersMap.put(printer.getId(), printer);
+        }
+
+        ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
+                new ArrayMap<PrinterId, PrinterInfo>();
+        final int favoritePrinterCount = favoritePrinters.size();
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            PrinterInfo favoritePrinter = favoritePrinters.get(i);
+            favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter);
+        }
+
+        computeAndDeliverResult(printersMap, favoritePrintersMap);
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onCancelLoad() " + FusedPrintersProvider.this.hashCode());
+        }
+        return cancelInternal();
+    }
+
+    private boolean cancelInternal() {
+        if (mDiscoverySession != null
+                && mDiscoverySession.isPrinterDiscoveryStarted()) {
+            if (mTrackedPrinter != null) {
+                mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
+                mTrackedPrinter = null;
+            }
+            mDiscoverySession.stopPrinterDiscovery();
+            return true;
+        } else if (mPersistenceManager.isReadHistoryInProgress()) {
+            return mPersistenceManager.stopReadPrinterHistory();
+        }
+        return false;
+    }
+
+    @Override
+    protected void onReset() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onReset() " + FusedPrintersProvider.this.hashCode());
+        }
+        onStopLoading();
+        mPrinters.clear();
+        if (mDiscoverySession != null) {
+            mDiscoverySession.destroy();
+            mDiscoverySession = null;
+        }
+    }
+
+    @Override
+    protected void onAbandon() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onAbandon() " + FusedPrintersProvider.this.hashCode());
+        }
+        onStopLoading();
+    }
+
+    public void setTrackedPrinter(PrinterId printerId) {
+        if (isStarted() && mDiscoverySession != null
+                && mDiscoverySession.isPrinterDiscoveryStarted()) {
+            if (mTrackedPrinter != null) {
+                if (mTrackedPrinter.equals(printerId)) {
+                    return;
+                }
+                mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
+            }
+            mTrackedPrinter = printerId;
+            if (printerId != null) {
+                mDiscoverySession.startPrinterStateTracking(printerId);
+            }
+        }
+    }
+
+    public boolean isFavoritePrinter(PrinterId printerId) {
+        final int printerCount = mFavoritePrinters.size();
+        for (int i = 0; i < printerCount; i++) {
+            PrinterInfo favoritePritner = mFavoritePrinters.get(i);
+            if (favoritePritner.getId().equals(printerId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void forgetFavoritePrinter(PrinterId printerId) {
+        List<PrinterInfo> newFavoritePrinters = null;
+
+        // Remove the printer from the favorites.
+        final int favoritePrinterCount = mFavoritePrinters.size();
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+            if (favoritePrinter.getId().equals(printerId)) {
+                newFavoritePrinters = new ArrayList<PrinterInfo>();
+                newFavoritePrinters.addAll(mPrinters);
+                newFavoritePrinters.remove(i);
+                break;
+            }
+        }
+
+        // If we removed a favorite printer, we have work to do.
+        if (newFavoritePrinters != null) {
+            // Remove the printer from history and persist the latter.
+            mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
+
+            // Recompute and deliver the printers.
+            updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
+        }
+    }
+
+    private final class PersistenceManager {
+        private static final String PERSIST_FILE_NAME = "printer_history.xml";
+
+        private static final String TAG_PRINTERS = "printers";
+
+        private static final String TAG_PRINTER = "printer";
+        private static final String TAG_PRINTER_ID = "printerId";
+
+        private static final String ATTR_LOCAL_ID = "localId";
+        private static final String ATTR_SERVICE_NAME = "serviceName";
+
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_DESCRIPTION = "description";
+        private static final String ATTR_STATUS = "status";
+
+        private final AtomicFile mStatePersistFile;
+
+        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
+
+        private boolean mReadHistoryCompleted;
+        private boolean mReadHistoryInProgress;
+
+        private ReadTask mReadTask;
+
+        private volatile long mLastReadHistoryTimestamp;
+
+        private PersistenceManager(Context context) {
+            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+                    PERSIST_FILE_NAME));
+        }
+
+        public boolean isReadHistoryInProgress() {
+            return mReadHistoryInProgress;
+        }
+
+        public boolean isReadHistoryCompleted() {
+            return mReadHistoryCompleted;
+        }
+
+        public boolean stopReadPrinterHistory() {
+            final boolean cancelled = mReadTask.cancel(true);
+            mReadTask = null;
+            return cancelled;
+        }
+
+        public void readPrinterHistory() {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "read history started "
+                        + FusedPrintersProvider.this.hashCode());
+            }
+            mReadHistoryInProgress = true;
+            mReadTask = new ReadTask();
+            mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        }
+
+        @SuppressWarnings("unchecked")
+        public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
+            if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
+                mHistoricalPrinters.remove(0);
+            }
+            mHistoricalPrinters.add(printer);
+            new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                    new ArrayList<PrinterInfo>(mHistoricalPrinters));
+        }
+
+        @SuppressWarnings("unchecked")
+        public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
+            boolean writeHistory = false;
+            final int printerCount = mHistoricalPrinters.size();
+            for (int i = printerCount - 1; i >= 0; i--) {
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                if (historicalPrinter.getId().equals(printerId)) {
+                    mHistoricalPrinters.remove(i);
+                    writeHistory = true;
+                }
+            }
+            if (writeHistory) {
+                new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                        new ArrayList<PrinterInfo>(mHistoricalPrinters));
+            }
+        }
+
+        public boolean isHistoryChanged() {
+            return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
+        }
+
+        private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
+            Map<PrinterId, PrinterRecord> recordMap =
+                    new ArrayMap<PrinterId, PrinterRecord>();
+
+            // Recompute the weights.
+            float currentWeight = 1.0f;
+            final int printerCount = printers.size();
+            for (int i = printerCount - 1; i >= 0; i--) {
+                PrinterInfo printer = printers.get(i);
+                // Aggregate weight for the same printer
+                PrinterRecord record = recordMap.get(printer.getId());
+                if (record == null) {
+                    record = new PrinterRecord(printer);
+                    recordMap.put(printer.getId(), record);
+                }
+                record.weight += currentWeight;
+                currentWeight *= WEIGHT_DECAY_COEFFICIENT;
+            }
+
+            // Soft the favorite printers.
+            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+                    recordMap.values());
+            Collections.sort(favoriteRecords);
+
+            // Write the favorites to the output.
+            final int favoriteCount = Math.min(favoriteRecords.size(),
+                    MAX_FAVORITE_PRINTER_COUNT);
+            List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+            for (int i = 0; i < favoriteCount; i++) {
+                PrinterInfo printer = favoriteRecords.get(i).printer;
+                favoritePrinters.add(printer);
+            }
+
+            return favoritePrinters;
+        }
+
+        private final class PrinterRecord implements Comparable<PrinterRecord> {
+            public final PrinterInfo printer;
+            public float weight;
+
+            public PrinterRecord(PrinterInfo printer) {
+                this.printer = printer;
+            }
+
+            @Override
+            public int compareTo(PrinterRecord another) {
+                return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
+            }
+        }
+
+        private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {
+            @Override
+            protected List<PrinterInfo> doInBackground(Void... args) {
+               return doReadPrinterHistory();
+            }
+
+            @Override
+            protected void onPostExecute(List<PrinterInfo> printers) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "read history completed "
+                            + FusedPrintersProvider.this.hashCode());
+                }
+
+                // Ignore printer records whose target services are not enabled.
+                PrintManager printManager = (PrintManager) getContext()
+                        .getSystemService(Context.PRINT_SERVICE);
+                List<PrintServiceInfo> services = printManager
+                        .getEnabledPrintServices();
+
+                Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();
+                final int installedServiceCount = services.size();
+                for (int i = 0; i < installedServiceCount; i++) {
+                    ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
+                    ComponentName componentName = new ComponentName(
+                            serviceInfo.packageName, serviceInfo.name);
+                    enabledComponents.add(componentName);
+                }
+
+                final int printerCount = printers.size();
+                for (int i = printerCount - 1; i >= 0; i--) {
+                    ComponentName printerServiceName = printers.get(i).getId().getServiceName();
+                    if (!enabledComponents.contains(printerServiceName)) {
+                        printers.remove(i);
+                    }
+                }
+
+                // Store the filtered list.
+                mHistoricalPrinters = printers;
+
+                // Compute the favorite printers.
+                mFavoritePrinters.clear();
+                mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters));
+
+                mReadHistoryInProgress = false;
+                mReadHistoryCompleted = true;
+
+                // Deliver the printers.
+                updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters);
+
+                // Loading the available printers if needed.
+                loadInternal();
+
+                // We are done.
+                mReadTask = null;
+            }
+
+            private List<PrinterInfo> doReadPrinterHistory() {
+                final FileInputStream in;
+                try {
+                    in = mStatePersistFile.openRead();
+                } catch (FileNotFoundException fnfe) {
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "No existing printer history "
+                                + FusedPrintersProvider.this.hashCode());
+                    }
+                    return new ArrayList<PrinterInfo>();
+                }
+                try {
+                    List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+                    XmlPullParser parser = Xml.newPullParser();
+                    parser.setInput(in, null);
+                    parseState(parser, printers);
+                    // Take a note which version of the history was read.
+                    mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
+                    return printers;
+                } catch (IllegalStateException ise) {
+                    Slog.w(LOG_TAG, "Failed parsing ", ise);
+                } catch (NullPointerException npe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", npe);
+                } catch (NumberFormatException nfe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", nfe);
+                } catch (XmlPullParserException xppe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", xppe);
+                } catch (IOException ioe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", ioe);
+                } catch (IndexOutOfBoundsException iobe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", iobe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                }
+
+                return Collections.emptyList();
+            }
+
+            private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
+                    throws IOException, XmlPullParserException {
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
+                parser.next();
+
+                while (parsePrinter(parser, outPrinters)) {
+                    // Be nice and respond to cancellation
+                    if (isCancelled()) {
+                        return;
+                    }
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
+            }
+
+            private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
+                    throws IOException, XmlPullParserException {
+                skipEmptyTextTags(parser);
+                if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
+                    return false;
+                }
+
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+                final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
+
+                parser.next();
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
+                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+                        null, ATTR_SERVICE_NAME));
+                PrinterId printerId =  new PrinterId(service, localId);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+                parser.next();
+
+                PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+                builder.setDescription(description);
+                PrinterInfo printer = builder.build();
+
+                outPrinters.add(printer);
+
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "[RESTORED] " + printer);
+                }
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+
+                return true;
+            }
+
+            private void expect(XmlPullParser parser, int type, String tag)
+                    throws IOException, XmlPullParserException {
+                if (!accept(parser, type, tag)) {
+                    throw new XmlPullParserException("Exepected event: " + type
+                            + " and tag: " + tag + " but got event: " + parser.getEventType()
+                            + " and tag:" + parser.getName());
+                }
+            }
+
+            private void skipEmptyTextTags(XmlPullParser parser)
+                    throws IOException, XmlPullParserException {
+                while (accept(parser, XmlPullParser.TEXT, null)
+                        && "\n".equals(parser.getText())) {
+                    parser.next();
+                }
+            }
+
+            private boolean accept(XmlPullParser parser, int type, String tag)
+                    throws IOException, XmlPullParserException {
+                if (parser.getEventType() != type) {
+                    return false;
+                }
+                if (tag != null) {
+                    if (!tag.equals(parser.getName())) {
+                        return false;
+                    }
+                } else if (parser.getName() != null) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
+            @Override
+            protected Void doInBackground(List<PrinterInfo>... printers) {
+                doWritePrinterHistory(printers[0]);
+                return null;
+            }
+
+            private void doWritePrinterHistory(List<PrinterInfo> printers) {
+                FileOutputStream out = null;
+                try {
+                    out = mStatePersistFile.startWrite();
+
+                    XmlSerializer serializer = new FastXmlSerializer();
+                    serializer.setOutput(out, "utf-8");
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, TAG_PRINTERS);
+
+                    final int printerCount = printers.size();
+                    for (int i = 0; i < printerCount; i++) {
+                        PrinterInfo printer = printers.get(i);
+
+                        serializer.startTag(null, TAG_PRINTER);
+
+                        serializer.attribute(null, ATTR_NAME, printer.getName());
+                        // Historical printers are always stored as unavailable.
+                        serializer.attribute(null, ATTR_STATUS, String.valueOf(
+                                PrinterInfo.STATUS_UNAVAILABLE));
+                        String description = printer.getDescription();
+                        if (description != null) {
+                            serializer.attribute(null, ATTR_DESCRIPTION, description);
+                        }
+
+                        PrinterId printerId = printer.getId();
+                        serializer.startTag(null, TAG_PRINTER_ID);
+                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+                                .flattenToString());
+                        serializer.endTag(null, TAG_PRINTER_ID);
+
+                        serializer.endTag(null, TAG_PRINTER);
+
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "[PERSISTED] " + printer);
+                        }
+                    }
+
+                    serializer.endTag(null, TAG_PRINTERS);
+                    serializer.endDocument();
+                    mStatePersistFile.finishWrite(out);
+
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "[PERSIST END]");
+                    }
+                } catch (IOException ioe) {
+                    Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
+                    mStatePersistFile.failWrite(out);
+                } finally {
+                    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/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
new file mode 100644
index 0000000..7715579
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -0,0 +1,609 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import com.android.printspooler.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an activity for selecting a printer.
+ */
+public final class SelectPrinterActivity extends Activity {
+
+    private static final String LOG_TAG = "SelectPrinterFragment";
+
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+    private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG =
+            "FRAGMENT_TAG_ADD_PRINTER_DIALOG";
+
+    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<>();
+
+    private PrinterRegistry mPrinterRegistry;
+
+    private ListView mListView;
+
+    private AnnounceFilterResult mAnnounceFilterResult;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getActionBar().setIcon(R.drawable.ic_menu_print);
+
+        setContentView(R.layout.select_printer_activity);
+
+        mPrinterRegistry = new PrinterRegistry(this, null);
+
+        // Hook up the list view.
+        mListView = (ListView) findViewById(android.R.id.list);
+        final DestinationAdapter adapter = new DestinationAdapter();
+        adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                if (!isFinishing() && adapter.getCount() <= 0) {
+                    updateEmptyView(adapter);
+                }
+            }
+
+            @Override
+            public void onInvalidated() {
+                if (!isFinishing()) {
+                    updateEmptyView(adapter);
+                }
+            }
+        });
+        mListView.setAdapter(adapter);
+
+        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) {
+                    return;
+                }
+
+                PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
+                onPrinterSelected(printer.getId());
+            }
+        });
+
+        registerForContextMenu(mListView);
+    }
+
+    @Override
+    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();
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String query) {
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String searchString) {
+                ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString);
+                return true;
+            }
+        });
+        searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View view) {
+                if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) {
+                    view.announceForAccessibility(getString(
+                            R.string.print_search_box_shown_utterance));
+                }
+            }
+            @Override
+            public void onViewDetachedFromWindow(View view) {
+                if (!isFinishing() && AccessibilityManager.getInstance(
+                        SelectPrinterActivity.this).isEnabled()) {
+                    view.announceForAccessibility(getString(
+                            R.string.print_search_box_hidden_utterance));
+                }
+            }
+        });
+
+        if (mAddPrinterServices.isEmpty()) {
+            menu.removeItem(R.id.action_add_printer);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        if (view == mListView) {
+            final int position = ((AdapterContextMenuInfo) menuInfo).position;
+            PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
+
+            menu.setHeaderTitle(printer.getName());
+
+            // Add the select menu item if applicable.
+            if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
+                MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer,
+                        Menu.NONE, R.string.print_select_printer);
+                Intent intent = new Intent();
+                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
+                selectItem.setIntent(intent);
+            }
+
+            // Add the forget menu item if applicable.
+            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();
+                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
+                forgetItem.setIntent(intent);
+            }
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.string.print_select_printer: {
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                onPrinterSelected(printerId);
+            } return true;
+
+            case R.string.print_forget_printer: {
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                mPrinterRegistry.forgetFavoritePrinter(printerId);
+            } return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateServicesWithAddPrinterActivity();
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public void onPause() {
+        if (mAnnounceFilterResult != null) {
+            mAnnounceFilterResult.remove();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.action_add_printer) {
+            showAddPrinterSelectionDialog();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    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) getSystemService(Context.PRINT_SERVICE);
+        List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
+
+        // No enabled print services - done.
+        if (enabledServices.isEmpty()) {
+            return;
+        }
+
+        // Find the services with valid add printers activities.
+        final int enabledServiceCount = enabledServices.size();
+        for (int i = 0; i < enabledServiceCount; i++) {
+            PrintServiceInfo enabledService = enabledServices.get(i);
+
+            // No add printers activity declared - next.
+            if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
+                continue;
+            }
+
+            ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
+            ComponentName addPrintersComponentName = new ComponentName(
+                    serviceInfo.packageName, enabledService.getAddPrintersActivityName());
+            Intent addPritnersIntent = new Intent()
+                .setComponent(addPrintersComponentName);
+
+            // The add printers activity is valid - add it.
+            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, getPackageName())
+                                        == PackageManager.PERMISSION_GRANTED)) {
+                    mAddPrinterServices.add(enabledService);
+                }
+            }
+        }
+    }
+
+    private void showAddPrinterSelectionDialog() {
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        Fragment oldFragment = getFragmentManager().findFragmentByTag(
+                FRAGMENT_TAG_ADD_PRINTER_DIALOG);
+        if (oldFragment != null) {
+            transaction.remove(oldFragment);
+        }
+        AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
+        Bundle arguments = new Bundle();
+        arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS,
+                mAddPrinterServices);
+        newFragment.setArguments(arguments);
+        transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG);
+        transaction.commit();
+    }
+
+    public void updateEmptyView(DestinationAdapter adapter) {
+        if (mListView.getEmptyView() == null) {
+            View emptyView = findViewById(R.id.empty_print_state);
+            mListView.setEmptyView(emptyView);
+        }
+        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);
+        } else {
+            titleView.setText(R.string.print_no_printers);
+            progressBar.setVisibility(View.GONE);
+        }
+    }
+
+    private void announceSearchResultIfNeeded() {
+        if (AccessibilityManager.getInstance(this).isEnabled()) {
+            if (mAnnounceFilterResult == null) {
+                mAnnounceFilterResult = new AnnounceFilterResult();
+            }
+            mAnnounceFilterResult.post();
+        }
+    }
+
+    public static class AddPrinterAlertDialogFragment extends DialogFragment {
+
+        private String mAddPrintServiceItem;
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                    .setTitle(R.string.choose_print_service);
+
+            final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
+                    getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS);
+
+            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++) {
+                PrintServiceInfo printService = printServices.get(i);
+                adapter.add(printService.getResolveInfo().loadLabel(
+                        getActivity().getPackageManager()).toString());
+            }
+
+            final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
+                    Settings.Secure.PRINT_SERVICE_SEARCH_URI);
+            final Intent viewIntent;
+            if (!TextUtils.isEmpty(searchUri)) {
+                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
+                if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
+                    viewIntent = intent;
+                    mAddPrintServiceItem = getString(R.string.add_print_service_label);
+                    adapter.add(mAddPrintServiceItem);
+                } else {
+                    viewIntent = null;
+                }
+            } else {
+                viewIntent = null;
+            }
+
+            builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    String item = adapter.getItem(which);
+                    if (item.equals(mAddPrintServiceItem)) {
+                        try {
+                            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(
+                                printService.getResolveInfo().serviceInfo.packageName,
+                                printService.getAddPrintersActivityName());
+                        Intent intent = new Intent(Intent.ACTION_MAIN);
+                        intent.setComponent(componentName);
+                        try {
+                            startActivity(intent);
+                        } catch (ActivityNotFoundException anfe) {
+                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
+                        }
+                    }
+                }
+            });
+
+            return builder.create();
+        }
+    }
+
+    private final class DestinationAdapter extends BaseAdapter implements Filterable {
+
+        private final Object mLock = new Object();
+
+        private final List<PrinterInfo> mPrinters = new ArrayList<>();
+
+        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>();
+
+        private CharSequence mLastSearchString;
+
+        public DestinationAdapter() {
+            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
+        public Filter getFilter() {
+            return new Filter() {
+                @Override
+                protected FilterResults performFiltering(CharSequence constraint) {
+                    synchronized (mLock) {
+                        if (TextUtils.isEmpty(constraint)) {
+                            return null;
+                        }
+                        FilterResults results = new FilterResults();
+                        List<PrinterInfo> filteredPrinters = new ArrayList<>();
+                        String constraintLowerCase = constraint.toString().toLowerCase();
+                        final int printerCount = mPrinters.size();
+                        for (int i = 0; i < printerCount; i++) {
+                            PrinterInfo printer = mPrinters.get(i);
+                            if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+                                filteredPrinters.add(printer);
+                            }
+                        }
+                        results.values = filteredPrinters;
+                        results.count = filteredPrinters.size();
+                        return results;
+                    }
+                }
+
+                @Override
+                @SuppressWarnings("unchecked")
+                protected void publishResults(CharSequence constraint, FilterResults results) {
+                    final boolean resultCountChanged;
+                    synchronized (mLock) {
+                        final int oldPrinterCount = mFilteredPrinters.size();
+                        mLastSearchString = constraint;
+                        mFilteredPrinters.clear();
+                        if (results == null) {
+                            mFilteredPrinters.addAll(mPrinters);
+                        } else {
+                            List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
+                            mFilteredPrinters.addAll(printers);
+                        }
+                        resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
+                    }
+                    if (resultCountChanged) {
+                        announceSearchResultIfNeeded();
+                    }
+                    notifyDataSetChanged();
+                }
+            };
+        }
+
+        public int getUnfilteredCount() {
+            synchronized (mLock) {
+                return mPrinters.size();
+            }
+        }
+
+        @Override
+        public int getCount() {
+            synchronized (mLock) {
+                return mFilteredPrinters.size();
+            }
+        }
+
+        @Override
+        public Object getItem(int position) {
+            synchronized (mLock) {
+                return mFilteredPrinters.get(position);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            return getView(position, convertView, parent);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(
+                        R.layout.printer_list_item, parent, false);
+            }
+
+            convertView.setEnabled(isActionable(position));
+
+            PrinterInfo printer = (PrinterInfo) getItem(position);
+
+            CharSequence title = printer.getName();
+            CharSequence subtitle = null;
+            Drawable icon = null;
+
+            try {
+                PackageManager pm = getPackageManager();
+                PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
+                        .getServiceName().getPackageName(), 0);
+                subtitle = packageInfo.applicationInfo.loadLabel(pm);
+                icon = packageInfo.applicationInfo.loadIcon(pm);
+            } 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.GONE);
+            }
+
+            return convertView;
+        }
+
+        public boolean isActionable(int position) {
+            PrinterInfo printer =  (PrinterInfo) getItem(position);
+            return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+        }
+    }
+
+    private final class AnnounceFilterResult implements Runnable {
+        private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
+
+        public void post() {
+            remove();
+            mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
+        }
+
+        public void remove() {
+            mListView.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            final int count = mListView.getAdapter().getCount();
+            final String text;
+            if (count <= 0) {
+                text = getString(R.string.print_no_printers);
+            } else {
+                text = getResources().getQuantityString(
+                    R.plurals.print_search_result_count_utterance, count, count);
+            }
+            mListView.announceForAccessibility(text);
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
new file mode 100644
index 0000000..912ee1d
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
@@ -0,0 +1,105 @@
+/*
+ * 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.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 final class MediaSizeUtils {
+
+    private static Map<MediaSize, String> sMediaSizeToStandardMap;
+
+    private MediaSizeUtils() {
+        /* do nothing - hide constructor */
+    }
+
+    /**
+     * Gets the default media size for the current locale.
+     *
+     * @param context Context for accessing resources.
+     * @return The default media size.
+     */
+    public static MediaSize getDefault(Context context) {
+        String mediaSizeId = context.getString(R.string.mediasize_default);
+        return MediaSize.getStandardMediaSizeById(mediaSizeId);
+    }
+
+    private static String getStandardForMediaSize(Context context, MediaSize mediaSize) {
+        if (sMediaSizeToStandardMap == null) {
+            sMediaSizeToStandardMap = new ArrayMap<MediaSize, String>();
+            String[] mediaSizeToStandardMapValues = context.getResources()
+                    .getStringArray(R.array.mediasize_to_standard_map);
+            final int mediaSizeToStandardCount = mediaSizeToStandardMapValues.length;
+            for (int i = 0; i < mediaSizeToStandardCount; i += 2) {
+                String mediaSizeId = mediaSizeToStandardMapValues[i];
+                MediaSize key = MediaSize.getStandardMediaSizeById(mediaSizeId);
+                String value = mediaSizeToStandardMapValues[i + 1];
+                sMediaSizeToStandardMap.put(key, value);
+            }
+        }
+        String standard = sMediaSizeToStandardMap.get(mediaSize);
+        return (standard != null) ? standard : context.getString(
+                R.string.mediasize_standard_iso);
+    }
+
+    /**
+     * Comparator for ordering standard media sizes. The ones for the current
+     * standard go to the top and the ones for the other standards follow grouped
+     * by standard. Media sizes of the same standard are ordered alphabetically.
+     */
+    public static final class MediaSizeComparator implements Comparator<MediaSize> {
+        private final Context mContext;
+
+        public MediaSizeComparator(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public int compare(MediaSize lhs, MediaSize rhs) {
+            String currentStandard = mContext.getString(R.string.mediasize_standard);
+            String lhsStandard = getStandardForMediaSize(mContext, lhs);
+            String rhsStandard = getStandardForMediaSize(mContext, rhs);
+
+            // The current standard always wins.
+            if (lhsStandard.equals(currentStandard)) {
+                if (!rhsStandard.equals(currentStandard)) {
+                    return -1;
+                }
+            } else if (rhsStandard.equals(currentStandard)) {
+                return 1;
+            }
+
+            if (!lhsStandard.equals(rhsStandard)) {
+                // Different standards - use the standard ordering.
+                return lhsStandard.compareTo(rhsStandard);
+            } else {
+                // Same standard - sort alphabetically by label.
+                return lhs.getLabel(mContext.getPackageManager()).
+                        compareTo(rhs.getLabel(mContext.getPackageManager()));
+            }
+        }
+    }
+}
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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e9cb197..e12549a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -267,6 +267,7 @@
             android:name=".DessertCaseDream"
             android:exported="true"
             android:label="@string/dessert_case"
+            android:permission="android.permission.BIND_DREAM_SERVICE"
             android:enabled="false"
             android:process=":sweetsweetdesserts"
             >
@@ -300,5 +301,12 @@
                 <category android:name="android.intent.category.DESK_DOCK" />
             </intent-filter>
         </activity>
+
+
+        <!-- I dream of notifications -->
+        <service
+            android:name=".doze.DozeService"
+            android:exported="true"
+            android:permission="android.permission.BIND_DREAM_SERVICE" />
     </application>
 </manifest>
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..305a82f 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
@@ -19,10 +19,10 @@
 
 <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:zAdjustment="normal">
+  <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/linear"
+         android:duration="200"/>
 </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..863591f 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
@@ -19,10 +19,10 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
-     android:zAdjustment="normal">
+     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/decelerate_cubic"
-         android:duration="250"/>
+         android:interpolator="@android:interpolator/linear_out_slow_in"
+         android:duration="200"/>
 </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..adcefe0
--- /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_linear_in"
+         android:duration="200"/>
+</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..863591f
--- /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/linear_out_slow_in"
+         android:duration="200"/>
+</set>
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png
index d4fdbf3..17100f7 100644
--- a/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png
new file mode 100644
index 0000000..e969d4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png
index 9fc1a3b..b53bd8f 100644
--- a/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png
+++ b/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png
new file mode 100644
index 0000000..657f710
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png
index f38de93..09606f6 100644
--- a/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png
new file mode 100644
index 0000000..a444c55
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png
index 8194605..427cad9 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png
new file mode 100644
index 0000000..29cf44b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml
index a7e8514..4a4c1c1 100644
--- a/packages/SystemUI/res/drawable/ic_account_circle.xml
+++ b/packages/SystemUI/res/drawable/ic_account_circle.xml
@@ -22,7 +22,13 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"/>
 
+    <group
+        android:scaleX="1.2"
+        android:scaleY="1.2"
+        android:pivotX="12.0"
+        android:pivotY="12.0">
     <path
         android:fill="#FFFFFFFF"
         android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,5.0c1.7,0.0 3.0,1.3 3.0,3.0c0.0,1.7 -1.3,3.0 -3.0,3.0c-1.7,0.0 -3.0,-1.3 -3.0,-3.0C9.0,6.3 10.3,5.0 12.0,5.0zM12.0,19.2c-2.5,0.0 -4.7,-1.3 -6.0,-3.2c0.0,-2.0 4.0,-3.1 6.0,-3.1c2.0,0.0 6.0,1.1 6.0,3.1C16.7,17.9 14.5,19.2 12.0,19.2z"/>
+    </group>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_notify_zen.xml b/packages/SystemUI/res/drawable/ic_notify_zen.xml
deleted file mode 100644
index c46455b..0000000
--- a/packages/SystemUI/res/drawable/ic_notify_zen.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size
-        android:width="24dp"
-        android:height="24dp"/>
-
-    <viewport
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0"/>
-
-    <path
-        android:fill="#FFFFFFFF"
-        android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/notification_scrim.xml b/packages/SystemUI/res/drawable/notification_scrim.xml
new file mode 100644
index 0000000..ff7e31f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_scrim.xml
@@ -0,0 +1,22 @@
+<?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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#34000000" />
+    <corners android:radius="@*android:dimen/notification_material_rounded_rect_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/recents_button_bg.xml b/packages/SystemUI/res/drawable/recents_button_bg.xml
new file mode 100644
index 0000000..a4cb088
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_button_bg.xml
@@ -0,0 +1,18 @@
+<?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="?android:attr/colorControlHighlight" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
new file mode 100644
index 0000000..5648065
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/keyguard_user_switcher"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="end"
+        android:visibility="gone"
+        android:paddingTop="4dp">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
new file mode 100644
index 0000000..691a80e
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -0,0 +1,40 @@
+<?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"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:layout_marginEnd="8dp"
+        android:gravity="center_vertical"
+        android:clickable="true"
+        android:background="@drawable/ripple_drawable">
+    <TextView android:id="@+id/name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="16dp"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.UserName"
+            />
+    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/picture"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:contentDescription="@null"
+            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
+            sysui:activeFrameColor="@color/current_user_border_color" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml
new file mode 100644
index 0000000..47740ee
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents.xml
@@ -0,0 +1,56 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent" 
+    android:layout_height="match_parent">
+    <!-- Status Bar Scrim View -->
+    <ImageView
+        android:id="@+id/status_bar_scrim"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|top"
+        android:scaleType="fitXY"
+        android:src="@drawable/recents_status_gradient" />
+
+    <!-- Recents View -->
+    <com.android.systemui.recents.views.RecentsView
+        android:id="@+id/recents_view"
+        android:layout_width="match_parent" 
+        android:layout_height="match_parent"
+        android:focusable="true" />
+
+    <!-- Empty View -->
+    <ViewStub android:id="@+id/empty_view_stub"
+           android:layout="@layout/recents_empty"
+           android:layout_width="match_parent"
+           android:layout_height="match_parent" />
+
+    <!-- Fullscreen Transition View -->
+    <ViewStub android:id="@+id/fullscreen_overlay_stub"
+           android:layout="@layout/recents_fullscreen_overlay"
+           android:layout_width="match_parent"
+           android:layout_height="match_parent" />
+
+    <!-- Nav Bar Scrim View -->
+    <ImageView
+        android:id="@+id/nav_bar_scrim"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:scaleType="fitXY"
+        android:src="@drawable/recents_lower_gradient" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_empty.xml b/packages/SystemUI/res/layout/recents_empty.xml
index ac6450b..21d1711 100644
--- a/packages/SystemUI/res/layout/recents_empty.xml
+++ b/packages/SystemUI/res/layout/recents_empty.xml
@@ -16,12 +16,13 @@
 
 <TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
     android:gravity="center"
     android:textSize="20sp"
     android:textColor="#ffffffff"
-    android:textStyle="italic"
     android:text="@string/recents_empty_message"
     android:fontFamily="sans-serif-light"
+    android:background="#80000000"
     android:visibility="gone" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_fullscreen_overlay.xml b/packages/SystemUI/res/layout/recents_fullscreen_overlay.xml
new file mode 100644
index 0000000..1d021f9
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_fullscreen_overlay.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<com.android.systemui.recents.views.FullscreenTransitionOverlayView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:visibility="gone">
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="fitXY" />
+</com.android.systemui.recents.views.FullscreenTransitionOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml b/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml
deleted file mode 100644
index 4245d49..0000000
--- a/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
--->
-
-<ImageView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_horizontal|bottom"
-    android:scaleType="fitXY"
-    android:src="@drawable/recents_lower_gradient" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 85d2f16..1bab67a 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -32,8 +32,10 @@
             android:id="@+id/application_icon"
             android:layout_width="@dimen/recents_task_view_application_icon_size"
             android:layout_height="@dimen/recents_task_view_application_icon_size"
-            android:layout_marginStart="16dp"
-            android:layout_gravity="center_vertical|start" />
+            android:layout_marginStart="8dp"
+            android:layout_gravity="center_vertical|start"
+            android:padding="8dp"
+            android:background="@drawable/recents_button_bg" />
         <TextView
             android:id="@+id/activity_description"
             android:layout_width="match_parent"
@@ -56,6 +58,8 @@
             android:layout_marginEnd="4dp"
             android:layout_gravity="center_vertical|end"
             android:padding="18dp"
+            android:background="@drawable/recents_button_bg"
+            android:visibility="invisible"
             android:src="@drawable/recents_dismiss_light" />
     </com.android.systemui.recents.views.TaskBarView>
 </com.android.systemui.recents.views.TaskView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index cde83bf..b54ba1a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -24,6 +24,7 @@
     android:id="@+id/notification_panel"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@android:color/transparent"
     >
 
     <include
@@ -91,6 +92,14 @@
 
     <include layout="@layout/status_bar_expanded_header" />
 
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
+        android:layout_gravity="end"
+        android:layout="@layout/keyguard_user_switcher" />
+
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 4028ec3..2e4c0ef 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -35,12 +35,66 @@
         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="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_alignWithParentIfMissing="true"
+        android:layout_marginStart="16dp"
+        android:paddingEnd="2dp" />
+
+    <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"
@@ -64,32 +118,6 @@
             android:layout_below="@id/clock"
             />
     </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"
@@ -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/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index c442f79..f0f50e1 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -49,4 +49,10 @@
         android:layout_width="120dp"
         android:layout_height="wrap_content"
         />
+
+    <com.android.systemui.statusbar.NotificationScrimView
+        android:id="@+id/scrim_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 5fabd3e..7663d54 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -48,4 +48,9 @@
         android:padding="2dp"
         />
 
+    <com.android.systemui.statusbar.NotificationScrimView
+        android:id="@+id/scrim_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.ExpandableNotificationRow>
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-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 3bf38cb..65ac2b1 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Geen onlangse programme nie"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Programinligting"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"soek"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Gelaai"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laai tans"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tot vol"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Laai nie"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Netwerk word\ndalk gemonitor"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Soek"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Gly op vir <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Gly links vir <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Kennisgewing versteek"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d kennisgewings versteek"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Raak om te wys"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Moenie steur nie"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Minder dringende kennisgewings hieronder"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Sleep links vir kamera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat jy dit afskakel"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Laai tans (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> tot vol)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gas"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ gas"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Een minuut lank"</item>
     <item quantity="other" msgid="6924190729213550991">"%d minute lank"</item>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index dea589d..8bed997 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"ምንም የቅርብ ጊዜ መተግበሪያዎች የሉም"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"የመተግበሪያ መረጃ"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ፈልግ"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ባትሪ ሞልቷል"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"ኃይል በመሙላት ላይ"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> እስኪሞላ ድረስ"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"ባትሪ እየሞላ አይደለም"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"አውታረ መረብ\nክትትል ሊደረግበት ይችላል"</string>
     <string name="description_target_search" msgid="3091587249776033139">"ፍለጋ"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"ለ<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ወደ ላይ አንሸራትት።"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"ለ<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ወደ ግራ አንሸራትት።"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"ማሳወቂያ ተደብቋል"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d ማሳወቂያዎች ተደብቀዋል"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"ለማሳየት ነካ ያድርጉ"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"አይረብሹ"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"በጣም አስቸካይ ያልሆኑ ማሳወቂያዎች ከታች"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"ለካሜራ ወደግራ ያንሸራትቱ"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ይህን እስኪያጠፉት ድረስ"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"ሃይል በመሙላት ላይ (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> እስከሚሞላ ድረስ)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"እንግዳ"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ እንግዳ"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"ለአንድ ደቂቃ"</item>
     <item quantity="other" msgid="6924190729213550991">"ለ%d ደቂቃዎች"</item>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8ecbc8f..2a3bcfb 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"ليست هناك تطبيقات حديثة"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"معلومات التطبيق"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"بحث"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"تم الشحن"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"جارٍ الشحن"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> حتى الاكتمال"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"لا يتم الشحن"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"قد تكون الشبكة\nخاضعة للرقابة"</string>
     <string name="description_target_search" msgid="3091587249776033139">"بحث"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"تمرير لأعلى لـ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"تمرير لليسار لـ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"تم إخفاء الإشعار"</item>
-    <item quantity="other" msgid="7388721375827338153">"‏تم إخفاء %d من الإشعارات"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"المس للعرض"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"الرجاء عدم الإزعاج"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"الإشعارات الأقل إلحاحًا أدناه"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"مرر سريعًا إلى اليمين لفتح الكاميرا"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"لحين تعطيل هذا الإعداد"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"جارٍ الشحن (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> حتى الامتلاء)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"المدعو"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ مدعو"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"لمدة دقيقة واحدة"</item>
     <item quantity="other" msgid="6924190729213550991">"‏لمدة %d من الدقائق"</item>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 2c3837a..5f777b4 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Няма скорошни приложения"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Информация за приложението"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"търсене"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заредена"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарежда се"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> до пълно зареждане"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Не се зарежда"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Мрежата може\nда се наблюдава"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Търсене"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Плъзнете нагоре за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Плъзнете наляво за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Известието е скрито"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d известия са скрити"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Докоснете за показване"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Не ме безпокойте"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Ппоказване на по-малко спешните известия по-долу"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Прекарайте пръст наляво, за да включите камерата"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Докато не изключите това"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Зарежда се (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> до пълно зареждане)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Гост"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ гост"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"За една минута"</item>
     <item quantity="other" msgid="6924190729213550991">"За %d минути"</item>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index c0b62d5..469b950 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"No hi ha aplicacions recents."</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informació de l\'aplicació"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"S\'està carregant"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> per completar la càrrega"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"No s\'està carregant"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"És possible que la xarxa\nestigui controlada"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Cerca"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Fes lliscar el dit cap amunt per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Fes lliscar el dit cap a l\'esquerra per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notificació oculta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notificacions ocultes"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Toca per mostrar-ho."</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"No molesteu"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificacions menys urgents a continuació"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Fes lliscar el dit cap a l\'esquerra per obrir la càmera."</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Fins que no ho desactivis"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Carregant (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> per completar la càrrega)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Convidat"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Convidat"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Durant un minut"</item>
     <item quantity="other" msgid="6924190729213550991">"Durant %d minuts"</item>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index f7e3b14..2d10a51 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Žádné nedávné aplikace"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informace o aplikaci"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"vyhledat"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabito"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nabíjení"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do plného nabití"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nenabíjí se"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Síť může být\nmonitorována"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Vyhledávání"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Přejeďte prstem nahoru: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Přejeďte prstem doleva: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Oznámení bylo skryto"</item>
-    <item quantity="other" msgid="7388721375827338153">"Skrytá oznámení: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Oznámení zobrazíte kliknutím"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Nerušit"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Méně urgentní oznámení níže"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Fotoaparát otevřete přejetím prstem vlevo."</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokud tuto funkci nevypnete"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Nabíjení (plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Host"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Přidat hosta"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Na jednu minutu"</item>
     <item quantity="other" msgid="6924190729213550991">"Na %d min"</item>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 05b72ce..16b12d0 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Der er ingen seneste apps"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Oplysninger om applikationen"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"søg"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opladet"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Oplader"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> indtil fuld opladet"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Oplader ikke"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Netværket kan\nvære overvåget"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Søgning"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Glid op for at <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Glid til venstre for at <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Underretningen er skjult"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d underretninger er skjult"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Tryk for at vise"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Vil ikke forstyrres"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mindre presserende underretninger nedenfor"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Stryg til venstre for at åbne kameraet"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Indtil du slår denne indstilling fra"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Opladning (fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gæst"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Gæst"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"I ét minut"</item>
     <item quantity="other" msgid="6924190729213550991">"I %d minutter"</item>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 6e6280c..79c7b25 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Keine neuen Apps"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"App-Info"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"Suche"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Aufgeladen"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Wird aufgeladen"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Voll in <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Wird nicht aufgeladen"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Netzwerk wird\neventuell überwacht."</string>
     <string name="description_target_search" msgid="3091587249776033139">"Suche"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Zum <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> nach oben schieben"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Zum <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> nach links schieben"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Benachrichtigung ausgeblendet"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d Benachrichtigungen ausgeblendet"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Zum Ansehen tippen"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Nicht stören"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Weniger dringende Benachrichtigungen unten"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Zum Öffnen der Kamera nach links wischen"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Bis zur Deaktivierung"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Wird aufgeladen (voll in <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gast"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Gast"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Für eine Minute"</item>
     <item quantity="other" msgid="6924190729213550991">"Für %d Minuten"</item>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index a99e91e..54a1dca 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Καμία πρόσφατη εφαρμογή"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Πληροφορίες εφαρμογής"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"αναζήτηση"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Φορτίστηκε"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Φόρτιση"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> για πλήρη φόρτιση"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Δεν φορτίζει"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Το δίκτυο μπορεί\nνα παρακολουθείται"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Αναζήτηση"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Κύλιση προς τα επάνω για <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Κύλιση προς τα αριστερά για <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Έγινε απόκρυψη της ειδοποίησης"</item>
-    <item quantity="other" msgid="7388721375827338153">"Έγινε απόκρυψη %d ειδοποιήσεων"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Αγγίξτε για εμφάνιση"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Μην ενοχλείτε"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Λιγότερο επείγουσες ειδοποιήσεις παρακάτω"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Σύρετε αριστερά για τη φωτογραφική μηχανή"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Μέχρι να το απενεργοποιήσετε"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Φόρτιση (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> για πλήρη φόρτιση)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Επισκέπτης"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Επισκέπτης"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Για ένα λεπτό"</item>
     <item quantity="other" msgid="6924190729213550991">"Για %d λεπτά"</item>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 0938b28..0852fa9 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"No recent apps"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Charging"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> until full"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Not charging"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Network may\nbe monitored"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Search"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notification hidden"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notifications hidden"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Touch to show"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Do not disturb"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Less urgent notifications below"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Swipe left for camera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charging (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> until full)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Guest"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Guest"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"For one minute"</item>
     <item quantity="other" msgid="6924190729213550991">"For %d minutes"</item>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 0938b28..0852fa9 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"No recent apps"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Application Info"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Charging"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> until full"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Not charging"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Network may\nbe monitored"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Search"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notification hidden"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notifications hidden"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Touch to show"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Do not disturb"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Less urgent notifications below"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Swipe left for camera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charging (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> until full)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Guest"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Guest"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"For one minute"</item>
     <item quantity="other" msgid="6924190729213550991">"For %d minutes"</item>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index cfa809c..ef89f37 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"No hay aplicaciones recientes."</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Cargando"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> para completarse"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"No se está cargando"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Es posible que la red\nesté supervisada."</string>
     <string name="description_target_search" msgid="3091587249776033139">"Buscar"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Desliza el dedo hacia arriba para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Desliza el dedo hacia la izquierda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notificación oculta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notificaciones ocultas"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Toca para mostrar"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"No molestar"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificaciones menos urgentes abajo"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Desliza hacia la izquierda para acceder a la cámara."</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta que lo desactives"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Cargando (faltan <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> para completar)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Invitado"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Agregar invitado"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Durante un minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Durante %d minutos"</item>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 242a7d0..7118322 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"No hay aplicaciones recientes"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Información de la aplicación"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Cargando"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> para completarse"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"No se está cargando"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"La red se\npuede supervisar"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Buscar"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Desliza el dedo hacia arriba para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Desliza el dedo hacia la izquierda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notification oculta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notificaciones ocultas"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Toca para mostrar"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"No molestar"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificaciones menos urgente abajo"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Desliza el dedo hacia la izquierda para acceder a la cámara"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta apagar el dispositivo"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Cargando (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> hasta completar)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Invitado"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Añadir invitado"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Durante un minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Durante %d minutos"</item>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index bc9a6e6..7968f320 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -221,13 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Hiljutisi rakendusi pole"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Rakenduste teave"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"otsing"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laetud"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laadimine"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Täislaadimiseks kulub <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Ei laadi"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Võrku võidakse\njälgida"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Otsing"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Lohistage üles: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Lohistage vasakule: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-    <!-- String.format failed for translation -->
-    <!-- no translation found for zen_mode_notification_title:other (7388721375827338153) -->
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Puudutage kuvamiseks"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Mitte segada"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Vähem kiireloomulised märguanded on allpool"</string>
@@ -237,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Kaamera kasutamiseks pühkige vasakule"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kuni lülitate selle välja"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Laadimine (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>, kuni seade on täis)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Külaline"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ külaline"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Üheks minutiks"</item>
     <item quantity="other" msgid="6924190729213550991">"%d minutiks"</item>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 8495828..e39564b 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"هیچ برنامه جدیدی موجود نیست"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"اطلاعات برنامه"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"جستجو"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"شارژ کامل شد"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"در حال شارژ شدن"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> مانده تا شارژ کامل شود"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"شارژ نمی‌شود"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"ممکن است شبکه\nتحت نظارت باشد"</string>
     <string name="description_target_search" msgid="3091587249776033139">"جستجو"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"لغزاندن به بالا برای <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"لغزاندن به چپ برای <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"اعلان پنهان شده"</item>
-    <item quantity="other" msgid="7388721375827338153">"‏%d اعلان پنهان شده"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"برای نمایش لمس کنید"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"مزاحم نشوید"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"اعلان‌های کمتر فوری در زیر"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"برای دوربین انگشت را تند به سمت راست بکشید"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"تا وقتی آن را خاموش کنید"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"در حال شارژ (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> تا شارژ کامل)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"مهمان"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ مهمان"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"برای یک دقیقه"</item>
     <item quantity="other" msgid="6924190729213550991">"‏برای %d دقیقه"</item>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index c73caf4..96f61dd 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Ei viimeaikaisia sovelluksia"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Sovellustiedot"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"haku"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ladattu"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Ladataan"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> kunnes täynnä"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Ei lataudu"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Verkkoa saatetaan\nvalvoa"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Haku"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Liu\'uta ylös ja <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Liu\'uta vasemmalle ja <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Ilmoitus piilotettu"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d ilmoitusta piilotettu"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Näytä koskettamalla"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Älä häiritse"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Vähemmän kiireelliset ilmoitukset ovat alla"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Avaa kamera pyyhkäisemällä oikealle"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kunnes poistat tämän käytöstä"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Ladataan (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> kunnes täynnä)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Vieras"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Vieras"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Minuutiksi"</item>
     <item quantity="other" msgid="6924190729213550991">"%d minuutiksi"</item>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 179b54d..ff192de 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Aucune application récente"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Détails de l\'application"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargée"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Charge en cours..."</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Chargée dans <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"N\'est pas en charge"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Le réseau peut\nêtre surveillé."</string>
     <string name="description_target_search" msgid="3091587249776033139">"Recherche"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Faire glisser le doigt vers le haut : <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Faites glisser votre doigt vers la gauche pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notification masquée"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notifications masquées"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Touchez pour afficher la notification"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ne pas déranger"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notifications moins urgentes affichées ci-dessous"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Balayez l\'écran vers la gauche pour accéder à l\'appareil photo"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charge en cours... (chargée à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Invité"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Ajouter un invité"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Pendant une minute"</item>
     <item quantity="other" msgid="6924190729213550991">"Pendant %d minutes"</item>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3334125..f0a1592 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Aucune application récente"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informations sur l\'application"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargé"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"En charge"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Chargé dans <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"N\'est pas en charge"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Le réseau peut\nêtre surveillé."</string>
     <string name="description_target_search" msgid="3091587249776033139">"Rechercher"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Faites glisser vers le haut pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Faites glisser vers la gauche pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notification masquée"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notifications masquées"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Appuyer pour afficher"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ne pas déranger"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notifications moins urgentes ci-dessous"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Balayer l\'écran vers la gauche pour accéder à l\'appareil photo"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charge en cours… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Invité"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Ajouter un invité"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Pendant une minute"</item>
     <item quantity="other" msgid="6924190729213550991">"Pendant %d minutes"</item>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fe28ec5..70d5413 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"कोई हाल ही का ऐप्स नहीं"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"एप्‍लिकेशन जानकारी"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"खोज"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज हो गई है"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"चार्ज हो रही है"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"पूर्ण होने में <xliff:g id="CHARGING_TIME">%s</xliff:g> शेष"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"चार्ज नहीं हो रही है"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"नेटवर्क को\nमॉनीटर किया जा सकता है"</string>
     <string name="description_target_search" msgid="3091587249776033139">"खोजें"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> के लिए ऊपर स्‍लाइड करें."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> के लिए बाएं स्‍लाइड करें."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"सूचना छिपी हुई है"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d सूचनाएं छिपी हुई हैं"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"दिखाने के लिए स्पर्श करें"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"परेशान न करें"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"कम अत्यावश्यक सूचनाएं नीचे दी गई हैं"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"कैमरे के लिए बाएं स्वाइप करें"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"जब तक आप इसे बंद नहीं कर देते"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"चार्ज हो रहा है (पूर्ण होने में <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> शेष)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"अतिथि"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ अतिथि"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"एक मिनट के लिए"</item>
     <item quantity="other" msgid="6924190729213550991">"%d मिनट के लिए"</item>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4ee3e32..034328d 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nema nedavnih aplikacija"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacije o aplikaciji"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"pretraži"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Punjenje"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do napunjenosti"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Ne puni se"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Mreža se\nmožda prati"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Pretraživanje"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Kliznite prema gore za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Kliznite lijevo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Obavijest je skrivena"</item>
-    <item quantity="other" msgid="7388721375827338153">"Broj skrivenih obavijesti: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Dodirnite za prikaz"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ne ometaj"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Manje hitne obavijesti pri dnu"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Prijeđite prstom ulijevo za fotoaparat"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dok ne isključite"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Punjenje (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> do napunjenosti)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gost"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ gost"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Jednu minutu"</item>
     <item quantity="other" msgid="6924190729213550991">"%d min"</item>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 95af816..e8d7257 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nincsenek nemrég használt alkalmazások"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Az alkalmazás adatai"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"keresés"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Feltöltve"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Töltés"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> a teljes töltöttségig"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nem töltődik"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Lehet, hogy a\nhálózat felügyelt"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Keresés"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"A(z) <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> művelethez csúsztassa felfelé."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"A(z) <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> művelethez csúsztassa balra."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Értesítés elrejtve"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d értesítés elrejtve"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"A megtekintéshez érintse meg"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ne zavarjanak"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"A kevésbé sürgős értesítések lentebb vannak"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"A fényképezőgép eléréséhez csúsztassa ujját balra"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Amíg ki nem kapcsolja ezt"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Töltés (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> a teljes töltöttségig)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Vendég"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ vendég"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Egy percen át"</item>
     <item quantity="other" msgid="6924190729213550991">"%d percen át"</item>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 637d85f..e7234ac8 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Նոր հավելվածներ չկան"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Հավելվածի մասին"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"որոնել"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Լիցքավորված է"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Լիցքավորվում է"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Լրիվ լիցքավորմանը մնաց <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Չի լիցքավորվում"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Ցանցը կարող է\nվերահսկվել"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Որոնել"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Սահեցրեք վերև <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-ի համար:"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Սահեցրեք ձախ` <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-ի համար:"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Ծանուցումը թաքցված է"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d ծանուցում թաքցված է"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Հպեք՝ ցուցադրելու համար"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Չխանգարել"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Պակաս հրատապ ծանուցումները ստորև"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Խցիկի համար սահեցրեք ձախ"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Քանի դեռ չեք անջատել"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> մինչև լրիվ լիցքավորումը)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Հյուր"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Հյուր"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Մեկ րոպե"</item>
     <item quantity="other" msgid="6924190729213550991">"%d րոպե"</item>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 3cd82a7..66b2ce5 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Tidak ada aplikasi terkini"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Info Aplikasi"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"telusuri"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Terisi"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Mengisi daya"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> sampai penuh"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Tidak mengisi daya"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Jaringan bisa\ndiawasi"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Telusuri"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Geser ke atas untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Geser ke kiri untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Pemberitahuan disembunyikan"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d pemberitahuan disembunyikan"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Sentuh untuk menampilkan"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Jangan ganggu"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Pemberitahuan kurang darurat di bawah"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Gesek ke kiri untuk kamera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hingga Anda menonaktifkan ini"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Mengisi daya (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> hingga penuh)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Tamu"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Tamu"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Selama satu menit"</item>
     <item quantity="other" msgid="6924190729213550991">"Selama %d menit"</item>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index b62d75a..3a09f20 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nessuna app recente"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informazioni sull\'applicazione"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carica"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"In carica"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> al termine della carica"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Non in carica"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"La rete potrebbe\nessere monitorata"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Ricerca"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Su per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"A sinistra per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notifica nascosta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notifiche nascoste"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Tocca per visualizzare"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Non disturbare"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notifiche meno urgenti in basso"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Scorri verso sinistra per accedere alla fotocamera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Fino alla disattivazione"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"In carica (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> al termine)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Ospite"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ ospite"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Per un minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Per %d minuti"</item>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index e1e7e00..629f3ac 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"אין אפליקציות אחרונות"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"מידע על האפליקציה"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"חפש"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"טעון"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"טוען"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> עד למילוי"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"לא בטעינה"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"ייתכן שהרשת\nמנוטרת"</string>
     <string name="description_target_search" msgid="3091587249776033139">"חיפוש"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"הסט למעלה כדי להציג <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"הסט שמאלה כדי להציג <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"ההודעה הוסתרה"</item>
-    <item quantity="other" msgid="7388721375827338153">"‏%d הודעות הוסתרו"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"גע כדי להציג"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"נא לא להפריע"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"הודעות בדחיפות נמוכה יותר בהמשך"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"החלק שמאלה להפעלת המצלמה"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"עד שתכבה"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"טוען (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> עד לסיום)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"אורח"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ אורח"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"למשך דקה אחת"</item>
     <item quantity="other" msgid="6924190729213550991">"‏למשך %d דקות"</item>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index b6359e9..113023a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"最近使ったアプリはありません"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"アプリ情報"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"検索"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"充電が完了しました"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電しています"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"充電完了まで<xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"充電していません"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"ネットワークが監視される\n場合があります"</string>
     <string name="description_target_search" msgid="3091587249776033139">"検索します"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"上にスライドして<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>を行います。"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"左にスライドして<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>を行います。"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"通知が非表示"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d件の通知が非表示"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"表示するにはタップします"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"通知を非表示"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"緊急度の低い通知を下に表示"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"左にスワイプしてカメラを表示"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ユーザーがOFFにするまで"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"充電中(フルになるまで<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"ゲスト"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ ゲスト"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1分"</item>
     <item quantity="other" msgid="6924190729213550991">"%d分"</item>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index 973cd63..bbcec3f 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"ბოლო აპები არ არის"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"აპლიკაციის შესახებ"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ძიება"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"დატენილია"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"მიმდინარეობს დატენვა"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> სრულად დატენვამდე"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"არ იტენება"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"შესაძლოა ქსელზე\nმონიტორინგი ხორციელდებოდეს"</string>
     <string name="description_target_search" msgid="3091587249776033139">"ძიება"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"გაასრიალეთ ზემოთ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-თვის."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"გაასრიალეთ მარცხნივ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-თვის."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"შეტყობინება დამალულია"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d შეტყობინება დამალულია"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"შეეხეთ საჩვენებლად"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"არ შემაწუხოთ"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"ქვემოთ მითითებულია ნაკლებად სასწრაფო შეტყობინებები"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"კამერისთვის მარცხენა შენაცვლება"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"სანამ ამას გამორთავდეთ"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"(<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>-ის შეცვლა დასრულებამდე)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"სტუმარი"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ სტუმარი"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"ერთი წუთით"</item>
     <item quantity="other" msgid="6924190729213550991">"%d წუთით"</item>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 8b0e3b1..938d854 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"មិនមាន​​កម្មវិធី​ថ្មីៗ"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"ព័ត៌មាន​កម្មវិធី"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ស្វែងរក"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"បាន​បញ្ចូល​ថ្ម"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"កំពុង​បញ្ចូល​ថ្ម"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> រហូត​ដល់ពេញ"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"មិន​កំពុង​បញ្ចូល​ថ្ម"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"បណ្ដាញ​អាច​\nត្រូវ​បាន​ត្រួតពិនិត្យ"</string>
     <string name="description_target_search" msgid="3091587249776033139">"ស្វែងរក"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"រុញ​ឡើង​លើ​ដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"រុញ​ទៅ​ឆ្វេង​ដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"បាន​លាក់​ការ​ជូនដំណឹង"</item>
-    <item quantity="other" msgid="7388721375827338153">"បាន​លាក់ការ​ជូនដំណឹង %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"ប៉ះ​ដើម្បី​បង្ហាញ"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"កុំ​រំខាន"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"ការ​ជូន​ដំណឹង​​មិន​សូវ​បន្ទាន់​ខាង​ក្រោម"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"អូស​ទៅ​ឆ្វេង​​ដើម្បី​ប្រើ​​ម៉ាស៊ីន​ថត"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"រហូត​ដល់ពេល​​អ្នក​បិទ​វា"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"កំពុង​បញ្ចូល​ថ្ម (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> ទើប​ពេញ)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"ភ្ញៀវ"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ ភ្ញៀវ"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"សម្រាប់​មួយ​នាទី"</item>
     <item quantity="other" msgid="6924190729213550991">"សម្រាប់ %d នាទី"</item>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 5435a7b..fa40aa5 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"최근에 사용한 앱 없음"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"애플리케이션 정보"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"검색"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"충전됨"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"충전 중"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"완충까지 <xliff:g id="CHARGING_TIME">%s</xliff:g> 남음"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"충전 안함"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"네트워크가\n모니터링될 수 있음"</string>
     <string name="description_target_search" msgid="3091587249776033139">"검색"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>하려면 위로 슬라이드"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>하려면 왼쪽으로 슬라이드"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"알림 숨김"</item>
-    <item quantity="other" msgid="7388721375827338153">"알림 %d개 숨김"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"표시하려면 터치"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"알림 일시중지"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"아래에 덜 급한 알림 표시"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"카메라를 사용하려면 왼쪽으로 스와이프하세요."</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"이 기능을 사용 중지할 때까지"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"충전 중(<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> 후 충전 완료)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"손님"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"새 손님 추가"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1분 동안"</item>
     <item quantity="other" msgid="6924190729213550991">"%d분 동안"</item>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 2671314..195cd60 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"ບໍ່​ມີ​ແອັບຯ​ທີ່​ຫາ​ກໍ​ໃຊ້"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"​ຂໍ້​ມູນ​ແອັບ​ພ​ລິ​ເຄ​ຊັນ"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ຊອກຫາ"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ສາກເຕັມແລ້ວ."</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"ກຳລັງສາກໄຟ"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ຈຶ່ງ​ຈະ​ເຕັມ"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"ບໍ່ໄດ້ສາກໄຟ"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"ເຄືອຄ່າຍອາດ\nຖືກຕິດຕາມ"</string>
     <string name="description_target_search" msgid="3091587249776033139">"ຊອກຫາ"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"ເລື່ອນຂຶ້ນເພື່ອ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"ເລື່ອນໄປທາງຊ້າຍເພື່ອ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"ເຊື່ອງ​ການ​ແຈ້ງ​ເຕ​ືອນ​ແລ້ວ"</item>
-    <item quantity="other" msgid="7388721375827338153">"ເຊື່ອງ %d ການ​ແຈ້ງ​ເຕືອນ​ແລ້ວ"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"​ແຕະ​ເພື່ອ​ສະ​ແດງ"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"ຫ້າມລົບກວນ"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"ການ​ແຈ້ງເຕືອນ​ທີ່​ສຳຄັນ​ໜ້ອຍ​ກວ່າ​ຢູ່​ດ້ານ​ລຸ່ມ"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"ປັດ​ຊ້າຍ​ເພື່ອ​ໃຊ້​ກ້ອງ"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"ຈົນກວ່າ​ທ່ານ​ຈະ​ປິດ"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"ກຳ​ລັງ​ສາກ​ໄຟ (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> ກວ່າ​ຈ​ະ​ເຕັມ)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"ແຂກ"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ ແຂກ"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"ເປັນ​ເວລາ​ນຶ່ງ​ນາ​ທີ"</item>
     <item quantity="other" msgid="6924190729213550991">"ເປັນ​ເວລາ %d ນາ​ທີ"</item>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 6563d45..792087c 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nėra naujausių programų"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Programos informacija"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"paieška"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Įkrautas"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Kraunamas"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> iki visiško įkrovimo"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Neįkraunamas"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Tinklas gali\nbūti stebimas"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Paieška"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Slyskite aukštyn link <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Slyskite į kairę link <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Pranešimas paslėptas"</item>
-    <item quantity="other" msgid="7388721375827338153">"Paslėpta pranešimų: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Palieskite, kad būtų rodoma"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Netrukdyti"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mažiau skubūs pranešimai toliau"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Perbraukite į kairę, kad būtų įjungtas fotoaparatas"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Kol išjungsite"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Kraunama (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> iki visiško įkrovimo)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Svečias"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Svečias"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1 min."</item>
     <item quantity="other" msgid="6924190729213550991">"%d min."</item>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 4c4031e..34f6919 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nav nesen izmantotu lietotņu"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informācija par lietojumprogrammu"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"Meklēt"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulators uzlādēts"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Notiek uzlāde"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> līdz pilnam akumulatoram"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nenotiek uzlāde"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Tīkls var\ntikt uzraudzīts"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Meklēt"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Velciet uz augšu, lai veiktu šādu darbību: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Velciet pa kreisi, lai veiktu šādu darbību: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Paziņojums paslēpts"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d paziņojumi paslēpti"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Pieskarieties, lai rādītu"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Netraucēt"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mazāk steidzami paziņojumi tiek rādīti tālāk"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Lai lietotu kameru, velciet pa kreisi."</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Līdz brīdim, kad izslēgsiet"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Notiek uzlāde (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> līdz pilnīgai uzlādei)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Viesis"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+Viesis"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Vienu minūti"</item>
     <item quantity="other" msgid="6924190729213550991">"%d min"</item>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 2bb37f6..13ca7d0 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Сүүлд ашигласан апп байхгүй"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Аппликешны мэдээлэл"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"хайх"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Цэнэглэгдсэн"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Цэнэглэж байна"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"дүүргэхэд <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Цэнэглэхгүй байна"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Сүлжээ хянагдаж\nбайж болзошгүй"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Хайх"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-г гулсуулах."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> хийх зүүнлүү гулсуулах."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Мэдэгдлийг нуусан"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d мэдэгдлийг нуусан"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Харуулах бол хүрнэ үү"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Бүү саад бол"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Яаралтай биш мэдэгдлүүдийг доор"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Камер гаргахын тулд зүүн шударна уу"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Таныг унтраах хүртэл"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Цэнэглэж байна (дүүргэхэд <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Зочин"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Зочин"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Нэг минутын турш"</item>
     <item quantity="other" msgid="6924190729213550991">"%d минутын турш"</item>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 7f46211..9496d3e 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Tiada apl terbaharu"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Maklumat Aplikasi"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"cari"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Sudah dicas"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Mengecas"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Lagi <xliff:g id="CHARGING_TIME">%s</xliff:g> untuk penuh"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Tidak mengecas"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Rangkaian mungkin\nboleh dipantau"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Carian"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Luncurkan ke atas untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Luncurkan ke kiri untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Pemberitahuan disembunyikan"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d pemberitahuan disembunyikan"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Sentuh untuk menunjukkan"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Jangan ganggu"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Pemberitahuan kurang penting di bawah"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Leret ke kiri untuk kamera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Sehingga anda matikan"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Mengecas (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> sehingga penuh)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Tetamu"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Tetamu"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Selama satu minit"</item>
     <item quantity="other" msgid="6924190729213550991">"Selama %d minit"</item>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 91b14da..04ef76b 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Ingen nylige apper"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformasjon"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"Søk"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Oppladet"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Lader"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Fulladet om <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Lader ikke"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Nettverket kan\nvære overvåket"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Søk"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Dra opp for å <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Dra til venstre for å <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Varselet er skjult"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d varsler er skjult"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Trykk for å vise"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ikke forstyrr"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mindre presserende varsler nedenfor"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Sveip mot venstre for å åpne kameraet"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Inntil du slår av funksjonen"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Lader (fulladet om <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gjest"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Gjest"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"I ett minutt"</item>
     <item quantity="other" msgid="6924190729213550991">"I %d minutter"</item>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index fd7337e..9dbe35c 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Geen recente apps"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"App-informatie"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"zoeken"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opgeladen"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Opladen"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tot volledig opgeladen"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Wordt niet opgeladen"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Netwerk kan\nworden gecontroleerd"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Zoeken"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Veeg omhoog voor <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Veeg naar links voor <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Melding verborgen"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d meldingen verborgen"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Raak aan om weer te geven"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Niet storen"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Minder urgente meldingen onderaan"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Veeg naar links voor camera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat u dit uitschakelt"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Opladen (vol over <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gast"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Gast"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Eén minuut"</item>
     <item quantity="other" msgid="6924190729213550991">"%d minuten"</item>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 4d582e2..d0e82b4 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Brak ostatnio uruchomionych aplikacji"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informacje o aplikacji"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"szukaj"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Naładowana"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Ładuje się"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do pełnego naładowania"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nie ładuje"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Sieć może być\nmonitorowana"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Szukaj"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Przesuń w górę: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Przesuń w lewo: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Powiadomienie ukryte"</item>
-    <item quantity="other" msgid="7388721375827338153">"Ukryte powiadomienia: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Dotknij, by zobaczyć"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Nie przeszkadzać"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Poniżej widać mniej pilne powiadomienia"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Przesuń w lewo, by przełączyć się na aparat"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dopóki nie wyłączysz"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Ładuje się (pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gość"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Dodaj gościa"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Przez minutę"</item>
     <item quantity="other" msgid="6924190729213550991">"Przez %d min"</item>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index d576ebb..441a45d 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nenhuma aplicação recente"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações da aplicação"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"A carregar"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> até ficar completa"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Não está a carregar"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"A rede pode ser\nmonitorizada"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Pesquisar"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Deslize para cima para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Deslize para a esquerda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notificação oculta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notificações ocultas"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Toque para mostrar"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Não incomodar"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificações menos urgentes abaixo"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Deslize rapidamente para a esquerda para aceder à câmara"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Até que o utilizador desative"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"A carregar (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> até à carga máxima)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Convidado"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Convidado"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Durante um minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Durante %d minutos"</item>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index caffae9..36654bc 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nenhum app recente"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informações do aplicativo"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Carregando"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> até concluir"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Não está carregando"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"A rede pode estar\nsob monitoração"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Pesquisar"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para cima."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para a esquerda."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notificação oculta"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d notificações ocultas"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Toque para mostrar"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Não perturbe"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificações menos urgentes abaixo"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Deslize para a esquerda para usar a câmera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Até você desativar"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Carregando (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> até concluir)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Convidado"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ convidado"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Por 1 minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Por %d minutos"</item>
diff --git a/packages/SystemUI/res/values-rm/strings.xml b/packages/SystemUI/res/values-rm/strings.xml
index 0a5a698..b082758 100644
--- a/packages/SystemUI/res/values-rm/strings.xml
+++ b/packages/SystemUI/res/values-rm/strings.xml
@@ -413,6 +413,14 @@
     <skip />
     <!-- no translation found for recents_search_bar_label (8074997400187836677) -->
     <skip />
+    <!-- no translation found for expanded_header_battery_charged (5945855970267657951) -->
+    <skip />
+    <!-- no translation found for expanded_header_battery_charging (205623198487189724) -->
+    <skip />
+    <!-- no translation found for expanded_header_battery_charging_with_time (457559884275395376) -->
+    <skip />
+    <!-- no translation found for expanded_header_battery_not_charging (4798147152367049732) -->
+    <skip />
     <!-- no translation found for ssl_ca_cert_warning (9005954106902053641) -->
     <skip />
     <!-- no translation found for description_target_search (3091587249776033139) -->
@@ -421,10 +429,6 @@
     <skip />
     <!-- no translation found for description_direction_left (7207478719805562165) -->
     <skip />
-    <!-- no translation found for zen_mode_notification_title:one (7809876956258040354) -->
-    <!-- no translation found for zen_mode_notification_title:other (7388721375827338153) -->
-    <!-- no translation found for zen_mode_notification_text (8336623711388065713) -->
-    <skip />
     <!-- no translation found for zen_mode_title (8793432092004749188) -->
     <skip />
     <!-- no translation found for keyguard_more_overflow_text (9195222469041601365) -->
@@ -443,6 +447,10 @@
     <skip />
     <!-- no translation found for keyguard_indication_charging_time (1757251776872835768) -->
     <skip />
+    <!-- no translation found for guest_nickname (8059989128963789678) -->
+    <skip />
+    <!-- no translation found for guest_new_guest (4259024453643879653) -->
+    <skip />
     <!-- no translation found for zen_mode_duration_minutes:one (9040808414992812341) -->
     <!-- no translation found for zen_mode_duration_minutes:other (6924190729213550991) -->
     <!-- no translation found for zen_mode_duration_hours:one (3480040795582254384) -->
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index cc2b111..94c9bd3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Nicio aplicație recentă"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informații despre aplicație"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"căutare"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"S-a încărcat"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Se încarcă"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> până la încărcare completă"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nu se încarcă"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Rețeaua poate\nfi monitorizată"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Căutaţi"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Glisaţi în sus pentru <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Glisaţi spre stânga pentru <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Notificare ascunsă"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d de notificări ascunse"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Atingeți pentru a afișa"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Nu deranjaţi"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Notificările mai puțin urgente mai jos"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Glisați la stânga pentru a accesa camera foto"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Până la dezactivare"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Se încarcă (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> până la finalizare)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Invitat"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Invitat"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Timp de un minut"</item>
     <item quantity="other" msgid="6924190729213550991">"Timp de %d (de) minute"</item>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index e940893..d0af456 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Ничего не найдено."</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Сведения о приложении"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"поиск"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Батарея заряжена"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарядка батареи"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> до полной зарядки"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Не заряжается"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Действия в сети\nмогут отслеживаться"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Поиск"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Проведите вверх, чтобы <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Проведите влево, чтобы <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Оповещение скрыто"</item>
-    <item quantity="other" msgid="7388721375827338153">"Скрыто оповещений: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Нажмите, чтобы открыть"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Не беспокоить"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Показать менее важные оповещения"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Чтобы включить камеру, пролистните влево"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Пока я не отключу"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Зарядка батареи (осталось <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Гость"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Добавить гостя"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1 мин."</item>
     <item quantity="other" msgid="6924190729213550991">"%d мин."</item>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index e917350..6883150 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Žiadne nedávne aplikácie"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Informácie o aplikácii"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"hľadať"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabitá"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nabíja sa"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Úplné nabitie o <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Nenabíja sa"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Sieť môže byť\nmonitorovaná"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Vyhľadávanie"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Prejdite prstom nahor: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Prejdite prstom doľava: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Upozornenie bolo skryté"</item>
-    <item quantity="other" msgid="7388721375827338153">"Skryté upozornenia: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Upozornenie zobrazíte dotykom"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Nerušiť"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Menej naliehavé upozornenia sa nachádzajú nižšie"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Fotoaparát otvoríte prejdením prstom doľava"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokým túto funkciu nevypnete"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Nabíja sa (úplné nabitie o <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Hosť"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Pridať hosťa"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Na jednu minútu"</item>
     <item quantity="other" msgid="6924190729213550991">"Na %d min"</item>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 0b117a1..a2de922 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Ni nedavnih aplikacij"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Podatki o aplikaciji"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"iskanje"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulator napolnjen"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Polnjenje"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do napolnjenosti"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Se ne polni"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Omrežje je\nlahko spremljano"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Iskanje"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Povlecite navzgor za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Povlecite v levo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Obvestilo je skrito"</item>
-    <item quantity="other" msgid="7388721375827338153">"Skritih je toliko obvestil: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Dotaknite se za prikaz"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ne moti"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Manj nujna obvestila spodaj"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Povlecite v levo za fotoaparat"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Dokler tega ne izklopite"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Polnjenje (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> do napolnjenosti)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gost"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Dodajanje gosta"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Za eno minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Za %d min"</item>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 4a49a7b..f378de0 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Нема недавних апликација"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Информације о апликацији"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"претражи"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Напуњена је"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Пуњење"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> док се не напуни"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Не пуни се"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Мрежа се можда\nнадгледа"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Претрага"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Превуците нагоре за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Превуците улево за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Обавештење је сакривено"</item>
-    <item quantity="other" msgid="7388721375827338153">"Сакривена обавештења: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Додирните за приказ"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Не узнемиравај"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Мање хитна обавештења су у наставку"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Превуците улево за камеру"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Док не искључите"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Пуњење (пун је за <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Гост"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Гост"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Један минут"</item>
     <item quantity="other" msgid="6924190729213550991">"%d мин"</item>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index d2024c4..50b62e1 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Inga aktiva appar"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Appinformation"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"sök"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laddat"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laddar"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tills batteriet är fulladdat"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Laddar inte"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Nätverket kan\nvara övervakat"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Sök"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Dra uppåt för <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Dra åt vänster för <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Aviseringen har dolts"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d aviseringar har dolts"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Tryck här om du vill visa aviseringar"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Stör ej"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mindre brådskande aviseringar nedan"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Dra åt vänster om du vill visa kameran"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Tills du inaktiverar detta"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Laddar (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> tills batteriet är fulladdat)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Gäst"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Lägg till gäst"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"I en minut"</item>
     <item quantity="other" msgid="6924190729213550991">"I %d minuter"</item>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index d42077f..33f16c4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -219,15 +219,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Hakuna programu za karibuni"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Maelezo ya Programu"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"tafuta"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Betri imejaa"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Inachaji"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> hadi ijae"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Haichaji"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Huenda mtandao\nunafuatiliwa"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Tafuta"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Sogeza juu kwa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Sogeza kushoto kwa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Arifa imefichwa"</item>
-    <item quantity="other" msgid="7388721375827338153">"Arifa %d zimefichwa"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Gusa ili zionekane"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Usisumbue"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>+"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Arifa zisizo za dharura sana ziko hapo chini"</string>
@@ -237,6 +236,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Telezesha kidole kushoto ili ufikie kamera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hadi utakapozima hili"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Inachaji ( <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> hadi ijae)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Aliyealikwa"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Aliyealikwa"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Kwa dakika moja"</item>
     <item quantity="other" msgid="6924190729213550991">"Kwa dakika %d"</item>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 47581a9..9f4c364 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,7 @@
     <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
          card. -->
     <integer name="keyguard_max_notification_count">5</integer>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ee4ba0e..ce9a98e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"ไม่มีแอปล่าสุด"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"ข้อมูลแอปพลิเคชัน"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ค้นหา"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ชาร์จแล้ว"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"กำลังชาร์จ"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"อีก <xliff:g id="CHARGING_TIME">%s</xliff:g> จึงจะเต็ม"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"ไม่ได้ชาร์จ"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"เครือข่ายอาจ\nถูกตรวจสอบ"</string>
     <string name="description_target_search" msgid="3091587249776033139">"ค้นหา"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"เลื่อนขึ้นเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"เลื่อนไปทางซ้ายเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"ซ่อนการแจ้งเตือนแล้ว"</item>
-    <item quantity="other" msgid="7388721375827338153">"ซ่อน %d การแจ้งเตือนแล้ว"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"แตะเพื่อแสดง"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"ห้ามรบกวน"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"การแจ้งเตือนที่เร่งด่วนน้อยด้านล่าง"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"กวาดไปทางซ้ายเพื่อใช้กล้องถ่ายรูป"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"จนกว่าคุณจะปิดฟังก์ชันนี้"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"กำลังชาร์จ (อีก <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> เต็ม)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"ผู้เข้าร่วม"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ ผู้เข้าร่วม"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1 นาที"</item>
     <item quantity="other" msgid="6924190729213550991">"%d นาที"</item>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 42e168e..1cdeec6 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Walang kamakailang mga app"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Impormasyon ng Application"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"maghanap"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nasingil na"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nagcha-charge"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> hanggang mapuno"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Hindi nagcha-charge"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Maaaring\nsinusubaybayan ang network"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Maghanap"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Mag-slide pataas para sa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Mag-slide pakaliwa para sa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Nakatago ang notification"</item>
-    <item quantity="other" msgid="7388721375827338153">"Nakatago ang %d (na) notification"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Pindutin upang ipakita"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Huwag istorbohin"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Nasa ibaba ang mga notification na hindi masyadong mahalaga"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Mag-swipe pakaliwa para sa camera"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Hanggang sa i-off mo ito"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Nagtsa-charge (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> hanggang mapuno)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Bisita"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Bisita"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Sa loob ng isang minuto"</item>
     <item quantity="other" msgid="6924190729213550991">"Sa loob ng %d (na) minuto"</item>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2967731..0daca74 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Yakın zamanda kullanılan uygulama yok"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Uygulama Bilgileri"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"ara"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ödeme alındı"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Şarj oluyor"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Tam şarj olmasına <xliff:g id="CHARGING_TIME">%s</xliff:g> kaldı"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Şarj olmuyor"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Ağ izleniyor\nolabilir"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Ara"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> için yukarı kaydırın."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> için sola kaydırın."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Bildirim gizli"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d bildirim gizli"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Görüntülemek için dokunun"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Rahatsız etmeyin"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Daha az acil bildirimler aşağıdadır"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Kamera için sola kaydırın"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Siz bunu kapatana kadar"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Şarj oluyor (tamamen dolmasına <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> kaldı)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Misafir"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Misafir"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Bir dakika süreyle"</item>
     <item quantity="other" msgid="6924190729213550991">"%d dakika süreyle"</item>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 43d4146f..e26bc37 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Немає останніх додатків"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Інформація про додаток"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"пошук"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заряджено"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Заряджається"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"До повного зарядження <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Не заряджається"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Мережа може\nвідстежуватися"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Пошук"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Проведіть пальцем угору, щоб <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Проведіть пальцем ліворуч, щоб <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Сповіщення сховано"</item>
-    <item quantity="other" msgid="7388721375827338153">"Сховано сповіщень: %d"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Торкніться, щоб показати"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Не турбувати"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Менше термінових сповіщень нижче"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Проведіть пальцем ліворуч, щоб скористатися камерою"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Доки ви не вимкнете"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Заряджання (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> до повного зарядження)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Гість"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"Додати гостя"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Протягом хвилини"</item>
     <item quantity="other" msgid="6924190729213550991">"Протягом %d хв"</item>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index eede966..28649e7 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Không có ứng dụng nào gần đây"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Thông tin ứng dụng"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"tìm kiếm"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Đã sạc"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Đang sạc"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> cho đến khi đầy"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Hiện không sạc"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Mạng có thể\nđược giám sát"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Tìm kiếm"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Trượt lên để <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Trượt sang trái để <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Thông báo ẩn"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d thông báo ẩn"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Chạm để hiển thị"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Không làm phiền"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Thông báo ít khẩn cấp hơn bên dưới"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Vuốt sang trái để mở máy ảnh"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Cho đến khi bạn tắt tính năng này"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Đang sạc (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> cho đến khi đầy)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Khách"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Khách"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Trong một phút"</item>
     <item quantity="other" msgid="6924190729213550991">"Trong %d phút"</item>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 61d59ab..e98e58d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"最近没有用过任何应用"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"应用信息"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"搜索"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"充电完成"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"正在充电"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"还需<xliff:g id="CHARGING_TIME">%s</xliff:g>才能充满"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"未在充电"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"网络可能会\n受到监控"</string>
     <string name="description_target_search" msgid="3091587249776033139">"搜索"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"向左滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"隐藏了1条通知"</item>
-    <item quantity="other" msgid="7388721375827338153">"隐藏了%d条通知"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"触摸即可显示"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"勿扰"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"不太紧急的通知会显示在下方"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"向左滑动可打开相机"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"直到您将其关闭"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"正在充电(还需<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>才能充满)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"访客"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"添加新访客"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1分钟"</item>
     <item quantity="other" msgid="6924190729213550991">"%d分钟"</item>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 8843363..ed44945 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"沒有最近使用的應用程式"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資料"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"已完成充電"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電中"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g>後完成充電"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"非充電中"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"網絡可能會\n受到監控"</string>
     <string name="description_target_search" msgid="3091587249776033139">"搜尋"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"向上滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"向左滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"已隱藏通知"</item>
-    <item quantity="other" msgid="7388721375827338153">"已隱藏 %d 則通知"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"輕觸即可顯示"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"請勿騷擾"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"還有 <xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g> 則通知"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"不太緊急的通知會在下方顯示"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"向左快速滑動即可使用相機功能"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"直至您關閉這項設定"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"充電中 (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>後完成充電)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"訪客"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"新增訪客"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1 分鐘"</item>
     <item quantity="other" msgid="6924190729213550991">"%d 分鐘"</item>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index a6efe21..be5ada4 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -223,15 +223,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"沒有最近使用的應用程式"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"應用程式資訊"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"已充飽"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電中"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g>後充飽"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"未充電"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"網路可能\n受到監控"</string>
     <string name="description_target_search" msgid="3091587249776033139">"搜尋"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"向上滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
     <string name="description_direction_left" msgid="7207478719805562165">"向左滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"已隱藏通知"</item>
-    <item quantity="other" msgid="7388721375827338153">"已隱藏 %d 則通知"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"輕觸即可顯示"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"請勿打擾"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"還有 <xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g> 則通知"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"較不緊急的通知會顯示在下方"</string>
@@ -241,6 +240,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"向左滑動可使用相機功能"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"手動關閉這項設定前一律啟用"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"充電中 (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>後充飽)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"訪客"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"新增訪客"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"1 分鐘"</item>
     <item quantity="other" msgid="6924190729213550991">"%d 分鐘"</item>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index bfeb47a..1dedda0 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -221,15 +221,14 @@
     <string name="recents_empty_message" msgid="7883614615463619450">"Azikho izinhlelo zokusebenza zakamuva"</string>
     <string name="recents_app_info_button_label" msgid="2890317189376000030">"Ulwazi lohlelo lokusebenza"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"sesha"</string>
+    <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Kushajiwe"</string>
+    <string name="expanded_header_battery_charging" msgid="205623198487189724">"Iyashaja"</string>
+    <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ize igcwale"</string>
+    <string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"Ayishaji"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Kungenzeka inethiwekhi\niqashiwe"</string>
     <string name="description_target_search" msgid="3091587249776033139">"Sesha"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"Shelelisela ngenhla ku-<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
     <string name="description_direction_left" msgid="7207478719805562165">"Shelelisela ngakwesokunxele ku-<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
-  <plurals name="zen_mode_notification_title">
-    <item quantity="one" msgid="7809876956258040354">"Isaziso sifihliwe"</item>
-    <item quantity="other" msgid="7388721375827338153">"%d izaziso zifihliwe"</item>
-  </plurals>
-    <string name="zen_mode_notification_text" msgid="8336623711388065713">"Thinta ukuze ubonise"</string>
     <string name="zen_mode_title" msgid="8793432092004749188">"Ungaphazamisi"</string>
     <string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Izaziso ezingasheshi kakhulu ezingezansi"</string>
@@ -239,6 +238,8 @@
     <string name="camera_hint" msgid="5241441720959174226">"Swayiphela ngakwesokunxele ukuze uthole ikhamela"</string>
     <string name="zen_mode_forever" msgid="7420011936770086993">"Uze uvale lokhu"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Iyashaja (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> ize igcwale)"</string>
+    <string name="guest_nickname" msgid="8059989128963789678">"Isihambeli"</string>
+    <string name="guest_new_guest" msgid="4259024453643879653">"+ Isihambeli"</string>
   <plurals name="zen_mode_duration_minutes">
     <item quantity="one" msgid="9040808414992812341">"Iminithi elilodwa"</item>
     <item quantity="other" msgid="6924190729213550991">"Amaminithi angu-%d"</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index c453618..8473d96 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -54,5 +54,10 @@
         <enum name="horizontal" value="0" />
         <enum name="vertical" value="1" />
     </attr>
+    <declare-styleable name="UserAvatarView">
+        <attr name="frameWidth" format="dimension" />
+        <attr name="activeFrameColor" format="color" />
+        <attr name="frameColor" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8c1a9c7..19d72c4 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -38,17 +38,15 @@
     <color name="system_secondary_color">#ff384248</color>
     <color name="system_accent_color">#ff7fcac3</color>
     <color name="system_error_color">#fff0592b</color>
-    <color name="qs_tile_divider">#29ffffff</color><!--  16% white -->
-    <color name="qs_tile_text">#FFFFFFFF</color>
+    <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
+    <color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
     <color name="status_bar_clock_color">#FFFFFFFF</color>
 
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff686868</color>
 
     <!-- The default recents task bar background color. -->
-    <color name="recents_task_bar_default_background_color">#e6444444</color>
-    <!-- The default recents task bar text color. -->
-    <color name="recents_task_bar_default_text_color">#ffeeeeee</color>
+    <color name="recents_task_bar_default_background_color">#ffe6e6e6</color>
     <!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_text_color">#ffeeeeee</color>
     <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
@@ -62,6 +60,9 @@
 
     <color name="keyguard_affordance">#ffffffff</color>
 
+    <!-- The color of the circle around the primary user in the user switcher -->
+    <color name="current_user_border_color">@color/primary_color</color>
+
     <!-- Our material color palette (deep teal) -->
     <color name="primary_color">#ff7fcac3</color>
     <color name="background_color_1">#ff384248</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1ef5bcd..c1f971a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -108,17 +108,25 @@
     <!-- milliseconds before the heads up notification accepts touches. -->
     <integer name="heads_up_sensitivity_delay">700</integer>
 
+    <!-- The duration in seconds to wait before the dismiss buttons are shown. -->
+    <integer name="recents_task_bar_dismiss_delay_seconds">1</integer>
     <!-- The min animation duration for animating views that are currently visible. -->
-    <integer name="recents_filter_animate_current_views_min_duration">175</integer>
+    <integer name="recents_filter_animate_current_views_duration">250</integer>
     <!-- The min animation duration for animating views that are newly visible. -->
-    <integer name="recents_filter_animate_new_views_min_duration">125</integer>
+    <integer name="recents_filter_animate_new_views_duration">250</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_exit_to_home_duration">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">275</integer>
+    <!-- The animation stagger to apply to each task animation when transitioning from home. -->
+    <integer name="recents_animate_task_enter_from_home_delay">10</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. -->
@@ -146,5 +154,8 @@
          Notification.tickerText across the status bar for what seems like an
          eternity. -->
     <bool name="enable_ticker">false</bool>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">false</bool>
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4a15363..370ff1c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -97,34 +97,6 @@
     <!-- half the distance between notifications in the panel -->
     <dimen name="notification_divider_height">2dp</dimen>
 
-    <!-- Notification drawer tuning parameters (phone UI) -->
-    <!-- Initial velocity of the shade when expanding on its own -->
-    <dimen name="self_expand_velocity">2000dp</dimen>
-    <!-- Initial velocity of the shade when collapsing on its own -->
-    <dimen name="self_collapse_velocity">2000dp</dimen>
-    <!-- Minimum final velocity of gestures interpreted as expand requests -->
-    <dimen name="fling_expand_min_velocity">100dp</dimen>
-    <!-- Minimum final velocity of gestures interpreted as collapse requests -->
-    <dimen name="fling_collapse_min_velocity">100dp</dimen>
-    <!-- Cap on contribution of x dimension of gesture to overall velocity -->
-    <dimen name="fling_gesture_max_x_velocity">200dp</dimen>
-    <!-- Cap on overall resulting fling speed (s^-1) -->
-    <dimen name="fling_gesture_max_output_velocity">3000dp</dimen>
-
-    <!-- Minimum distance a fling must travel (anti-jitter) -->
-    <dimen name="fling_gesture_min_dist">20dp</dimen>
-
-    <!-- Minimum fraction of the display a gesture must travel, at any velocity, to qualify as a
-         collapse request -->
-    <item type="dimen" name="collapse_min_display_fraction">10%</item>
-    <!-- Minimum fraction of the display a gesture must travel to qualify as an expand request -->
-    <item type="dimen" name="expand_min_display_fraction">50%</item>
-
-    <!-- Initial acceleration of an expand animation after fling -->
-    <dimen name="expand_accel">2000dp</dimen>
-    <!-- Initial acceleration of an collapse animation after fling -->
-    <dimen name="collapse_accel">2000dp</dimen>
-
     <!-- The padding on the global screenshot background image -->
     <dimen name="global_screenshot_bg_padding">20dp</dimen>
 
@@ -212,7 +184,7 @@
     <dimen name="glowpadview_inner_radius">15dip</dimen>
 
     <!-- The size of the application icon in the recents task view. -->
-    <dimen name="recents_task_view_application_icon_size">32dp</dimen>
+    <dimen name="recents_task_view_application_icon_size">48dp</dimen>
 
     <!-- The size of the activity icon in the recents task view. -->
     <dimen name="recents_task_view_activity_icon_size">60dp</dimen>
@@ -235,9 +207,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>
 
@@ -323,6 +292,9 @@
          device. -->
     <dimen name="unlock_move_distance">75dp</dimen>
 
+    <!-- Distance after which the scrim starts fading in when dragging down the quick settings -->
+    <dimen name="notification_scrim_wait_distance">100dp</dimen>
+
     <!-- Move distance for the unlock hint animation on the lockscreen -->
     <dimen name="hint_move_distance">75dp</dimen>
 
@@ -335,4 +307,11 @@
 
     <!-- end margin for multi user switch in expanded quick settings -->
     <dimen name="multi_user_switch_expanded_margin">8dp</dimen>
+
+    <!-- end margin for system icons if multi user switch is hidden -->
+    <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
+
+    <!-- The thickness of the colored border around the current user. -->
+    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 260f59c..f021253 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>
 
@@ -565,14 +577,6 @@
     <!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
     <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
 
-    <!-- Zen mode: Summary notification content title. [CHAR LIMIT=NONE] -->
-    <plurals name="zen_mode_notification_title">
-        <item quantity="one">Notification hidden</item>
-        <item quantity="other">%d notifications hidden</item>
-    </plurals>
-    <!-- Zen mode: Summary notification content text. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_notification_text">Touch to show</string>
-
     <!-- Zen mode: Short title. [CHAR LIMIT=40] -->
     <string name="zen_mode_title">Do not disturb</string>
 
@@ -602,6 +606,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 79a13f8..e5d5b03 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>
@@ -88,6 +100,13 @@
     <style name="TextAppearance.StatusBar.Expanded.Network.EmergencyOnly">
     </style>
 
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
+
     <style name="TextAppearance" />
     <style name="TextAppearance.QuickSettings" />
 
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/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
new file mode 100644
index 0000000..cc0d4a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -0,0 +1,238 @@
+/*
+ * 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.doze;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.service.dreams.DozeHardware;
+import android.service.dreams.DreamService;
+import android.util.Log;
+
+import com.android.systemui.SystemUIApplication;
+
+public class DozeService extends DreamService {
+    private static final boolean DEBUG = false;
+
+    private static final String TEASE_ACTION = "com.android.systemui.doze.tease";
+
+    private final String mTag = String.format("DozeService.%08x", hashCode());
+    private final Context mContext = this;
+
+    private Host mHost;
+    private DozeHardware mDozeHardware;
+    private SensorManager mSensors;
+    private Sensor mSigMotionSensor;
+    private PowerManager mPowerManager;
+    private PowerManager.WakeLock mWakeLock;
+    private boolean mDreaming;
+    private boolean mTeaseReceiverRegistered;
+
+    public DozeService() {
+        if (DEBUG) Log.d(mTag, "new DozeService()");
+        setDebug(DEBUG);
+    }
+
+    @Override
+    public void onCreate() {
+        if (DEBUG) Log.d(mTag, "onCreate");
+        super.onCreate();
+
+        if (getApplication() instanceof SystemUIApplication) {
+            final SystemUIApplication app = (SystemUIApplication) getApplication();
+            mHost = app.getComponent(Host.class);
+        }
+
+        setWindowless(true);
+
+        mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+        mSigMotionSensor = mSensors.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+        super.onAttachedToWindow();
+    }
+
+    @Override
+    public void onDreamingStarted() {
+        super.onDreamingStarted();
+        mDozeHardware = getDozeHardware();
+        if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze()
+                + " dozeHardware=" + mDozeHardware);
+        mDreaming = true;
+        listenForTeaseSignals(true);
+        requestDoze();
+    }
+
+    public void stayAwake(long millis) {
+        if (mDreaming && millis > 0) {
+            mWakeLock.acquire(millis);
+        }
+    }
+
+    public void startDozing() {
+        if (DEBUG) Log.d(mTag, "startDozing mDreaming=" + mDreaming);
+        if (!mDreaming) {
+            Log.w(mTag, "Not dozing, no longer dreaming");
+            return;
+        }
+
+        super.startDozing();
+    }
+
+    @Override
+    public void onDreamingStopped() {
+        if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing());
+        super.onDreamingStopped();
+
+        mDreaming = false;
+        mDozeHardware = null;
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+        listenForTeaseSignals(false);
+        stopDozing();
+        dozingStopped();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+        super.onDetachedFromWindow();
+
+        dozingStopped();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (DEBUG) Log.d(mTag, "onDestroy");
+        super.onDestroy();
+
+        dozingStopped();
+    }
+
+    private void requestDoze() {
+        if (mHost != null) {
+            mHost.requestDoze(this);
+        }
+    }
+
+    private void requestTease() {
+        if (mHost != null) {
+            mHost.requestTease(this);
+        }
+    }
+
+    private void dozingStopped() {
+        if (mHost != null) {
+            mHost.dozingStopped(this);
+        }
+    }
+
+    private void listenForTeaseSignals(boolean listen) {
+        if (DEBUG) Log.d(mTag, "listenForTeaseSignals: " + listen);
+        if (mHost == null) return;
+        listenForSignificantMotion(listen);
+        if (listen) {
+            mContext.registerReceiver(mTeaseReceiver, new IntentFilter(TEASE_ACTION));
+            mTeaseReceiverRegistered = true;
+            mHost.addCallback(mHostCallback);
+        } else {
+            if (mTeaseReceiverRegistered) {
+                mContext.unregisterReceiver(mTeaseReceiver);
+            }
+            mTeaseReceiverRegistered = false;
+            mHost.removeCallback(mHostCallback);
+        }
+    }
+
+    private void listenForSignificantMotion(boolean listen) {
+        if (mSigMotionSensor == null) return;
+        if (listen) {
+            mSensors.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+        } else {
+            mSensors.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+        }
+    }
+
+    private static String triggerEventToString(TriggerEvent event) {
+        if (event == null) return null;
+        final StringBuilder sb = new StringBuilder("TriggerEvent[")
+                .append(event.timestamp).append(',')
+                .append(event.sensor.getName());
+        if (event.values != null) {
+            for (int i = 0; i < event.values.length; i++) {
+                sb.append(',').append(event.values[i]);
+            }
+        }
+        return sb.append(']').toString();
+    }
+
+    private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+        @Override
+        public void onTrigger(TriggerEvent event) {
+            if (DEBUG) Log.d(mTag, "sigMotion.onTrigger: " + triggerEventToString(event));
+            if (DEBUG) {
+                final Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+                if (v != null) {
+                    v.vibrate(1000);
+                }
+            }
+            requestTease();
+            listenForSignificantMotion(true);  // reregister, this sensor only fires once
+        }
+    };
+
+    private final BroadcastReceiver mTeaseReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Log.d(mTag, "Received tease intent");
+            requestTease();
+        }
+    };
+
+    private final Host.Callback mHostCallback = new Host.Callback() {
+        @Override
+        public void onNewNotifications() {
+            if (DEBUG) Log.d(mTag, "onNewNotifications");
+            requestTease();
+        }
+    };
+
+    public interface Host {
+        void addCallback(Callback callback);
+        void removeCallback(Callback callback);
+        void requestDoze(DozeService dozeService);
+        void requestTease(DozeService dozeService);
+        void dozingStopped(DozeService dozeService);
+
+        public interface Callback {
+            void onNewNotifications();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c006c88..b9e2e1b 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)
 
     /**
@@ -245,6 +239,12 @@
     private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
     /**
+     * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+     * called.
+     * */
+    private boolean mHiding;
+
+    /**
      * we send this intent when the keyguard is dismissed.
      */
     private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT)
@@ -469,22 +469,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 +569,6 @@
             } else {
                 doKeyguardLocked(null);
             }
-            if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) {
-                mKeyguardAnalytics.getCallback().onScreenOff();
-            }
         }
         KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why);
     }
@@ -830,9 +811,6 @@
                 updateActivityLockScreenState();
                 adjustStatusBarLocked();
             }
-            if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) {
-                mKeyguardAnalytics.getCallback().onSetOccluded(isOccluded);
-            }
         }
     }
 
@@ -1197,6 +1175,7 @@
             }
 
             mStatusBarKeyguardViewManager.show(options);
+            mHiding = false;
             mShowing = true;
             mKeyguardDonePending = false;
             updateActivityLockScreenState();
@@ -1219,7 +1198,7 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
             try {
-
+                mHiding = true;
                 if (mShowing) {
 
                     // Don't actually hide the Keyguard at the moment, wait for window manager until
@@ -1240,6 +1219,11 @@
     private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         synchronized (KeyguardViewMediator.this) {
 
+            if (!mHiding) {
+                return;
+            }
+            mHiding = false;
+
             // only play "unlock" noises if not on a call (since the incall UI
             // disables the keyguard)
             if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index e8f3745..186b570 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -81,6 +81,8 @@
     private long mScreenOffTime;
     private int mShowing;
 
+    private long mBucketDroppedNegativeTimeMs;
+
     private boolean mSaver;
     private int mSaverTriggerLevel;
     private boolean mWarning;
@@ -108,6 +110,11 @@
     @Override
     public void update(int batteryLevel, int bucket, long screenOffTime) {
         mBatteryLevel = batteryLevel;
+        if (bucket >= 0) {
+            mBucketDroppedNegativeTimeMs = 0;
+        } else if (bucket < mBucket) {
+            mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+        }
         mBucket = bucket;
         mScreenOffTime = screenOffTime;
         mFallbackDialogs.update(batteryLevel, bucket, screenOffTime);
@@ -126,7 +133,7 @@
     }
 
     private void updateNotification() {
-        Slog.d(TAG, "updateNotification mWarning=" + mWarning
+        if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning
                 + " mSaver=" + mSaver + " mInvalidCharger=" + mInvalidCharger);
         if (mInvalidCharger) {
             showInvalidChargerNotification();
@@ -146,12 +153,14 @@
     private void showInvalidChargerNotification() {
         final Notification.Builder nb = new Notification.Builder(mContext)
                 .setSmallIcon(R.drawable.ic_power_low)
+                .setWhen(0)
                 .setShowWhen(false)
                 .setOngoing(true)
                 .setContentTitle(mContext.getString(R.string.invalid_charger_title))
                 .setContentText(mContext.getString(R.string.invalid_charger_text))
                 .setPriority(Notification.PRIORITY_MAX)
                 .setCategory(Notification.CATEGORY_SYSTEM)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_CHARGER), true);
         final Notification n = nb.build();
         if (n.headsUpContentView != null) {
@@ -165,12 +174,15 @@
                 : R.string.battery_low_percent_format;
         final Notification.Builder nb = new Notification.Builder(mContext)
                 .setSmallIcon(R.drawable.ic_power_low)
+                // Bump the notification when the bucket dropped.
+                .setWhen(mBucketDroppedNegativeTimeMs)
                 .setShowWhen(false)
                 .setContentTitle(mContext.getString(R.string.battery_low_title))
                 .setContentText(mContext.getString(textRes, mBatteryLevel))
                 .setOngoing(true)
                 .setPriority(Notification.PRIORITY_MAX)
                 .setCategory(Notification.CATEGORY_SYSTEM)
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_WARNING), true);
         if (hasBatterySettings()) {
             nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
@@ -196,8 +208,10 @@
                 .setContentTitle(mContext.getString(R.string.battery_saver_notification_title))
                 .setContentText(mContext.getString(R.string.battery_saver_notification_text))
                 .setOngoing(true)
+                .setWhen(0)
                 .setShowWhen(false)
-                .setCategory(Notification.CATEGORY_SYSTEM);
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setVisibility(Notification.VISIBILITY_PUBLIC);
         if (hasSaverSettings()) {
             nb.addAction(0,
                     mContext.getString(R.string.battery_saver_notification_action_text),
@@ -238,13 +252,13 @@
 
     @Override
     public void dismissLowBatteryWarning() {
-        Slog.i(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
+        if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
         dismissLowBatteryNotification();
         mFallbackDialogs.dismissLowBatteryWarning();
     }
 
     private void dismissLowBatteryNotification() {
-        Slog.i(TAG, "dismissing low battery notification");
+        if (mWarning) Slog.i(TAG, "dismissing low battery notification");
         mWarning = false;
         updateNotification();
     }
@@ -307,7 +321,7 @@
     }
 
     private void dismissInvalidChargerNotification() {
-        Slog.i(TAG, "dismissing invalid charger notification");
+        if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
         mInvalidCharger = false;
         updateNotification();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
index b981ed6..c4bdb19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java
@@ -69,6 +69,7 @@
 
             @Override
             public void onViewAttachedToWindow(View v) {
+                vp.updateStates();
                 volumeComponent.setVolumePanel(vp);
             }
         });
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 24a31f8..52ccb59 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
@@ -498,39 +492,52 @@
         Rect taskRect = getAnimationTaskRect(recentTasks);
         boolean useThumbnailTransition = !isTopTaskHome &&
                 hasValidTaskRects();
+        boolean hasRecentTasks = !recentTasks.isEmpty();
 
         if (useThumbnailTransition) {
             // 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;
             }
+        } else {
+            // If there is no thumbnail transition, but is launching from home into recents, then
+            // use a quick home transition and do the animation from home
+            if (hasRecentTasks) {
+                ActivityOptions opts = getHomeTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, EXTRA_FROM_HOME);
+            } else {
+                // Otherwise we do the normal fade from an unknown source
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, null);
+            }
         }
 
-        // 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 (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                    Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
         }
-
-        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 +546,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..4fb1918 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,12 +25,14 @@
         public static final boolean Verbose = false;
 
         public static class App {
+            // 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;
+            public static final boolean EnableTaskFiltering = true;
             // Enables clipping of tasks against each other
             public static final boolean EnableTaskStackClipping = true;
-            // Enables the use of theme colors as the task bar background
-            public static final boolean EnableTaskBarThemeColors = true;
+            // Enables tapping on the TaskBar to launch the task
+            public static final boolean EnableTaskBarTouchEvents = true;
             // Enables app-info pane on long-pressing the icon
             public static final boolean EnableDevAppInfoOnLongPress = true;
             // Enables the search bar layout
@@ -44,7 +46,7 @@
             // For debugging, this defines the number of mock recents packages to create
             public static final int SystemServicesProxyMockPackageCount = 3;
             // For debugging, this defines the number of mock recents tasks to create
-            public static final int SystemServicesProxyMockTaskCount = 75;
+            public static final int SystemServicesProxyMockTaskCount = 100;
         }
     }
 
@@ -52,8 +54,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;
@@ -86,10 +91,8 @@
         public static class App {
             public static int AppWidgetHostId = 1024;
             public static String Key_SearchAppWidgetId = "searchAppWidgetId";
-        }
-        public static class Window {
-            // The dark background dim is set behind the empty recents view
-            public static final float DarkBackgroundDim = 0.5f;
+            public static String Key_DebugModeEnabled = "debugModeEnabled";
+            public static String DebugModeVersion = "A";
         }
 
         public static class RecentsTaskLoader {
@@ -100,15 +103,6 @@
         public static class TaskStackView {
             public static final int TaskStackOverscrollRange = 150;
             public static final int FilterStartDelay = 25;
-
-            // The overlap height relative to the task height
-            public static final float StackOverlapPct = 0.65f;
-            // The height of the peek space relative to the stack height
-            public static final float StackPeekHeightPct = 0.1f;
-            // The min scale of the last card in the peek area
-            public static final float StackPeekMinScale = 0.8f;
-            // The number of cards we see in the peek space
-            public static final int StackPeekNumCards = 3;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
new file mode 100644
index 0000000..d90e2be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
@@ -0,0 +1,67 @@
+/*
+ * 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.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+/**
+ * A trigger for catching a debug chord.
+ * We currently use volume up then volume down to trigger this mode.
+ */
+public class DebugTrigger {
+
+    Handler mHandler;
+    Runnable mTriggeredRunnable;
+
+    int mLastKeyCode;
+    long mLastKeyCodeTime;
+
+    public DebugTrigger(Runnable triggeredRunnable) {
+        mHandler = new Handler();
+        mTriggeredRunnable = triggeredRunnable;
+    }
+
+    /** Resets the debug trigger */
+    void reset() {
+        mLastKeyCode = 0;
+        mLastKeyCodeTime = 0;
+    }
+
+    /**
+     * Processes a key event and tests if it is a part of the trigger. If the chord is complete,
+     * then we just call the callback.
+     */
+    public void onKeyEvent(int keyCode) {
+        if (mLastKeyCode == 0) {
+            if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+                mLastKeyCode = keyCode;
+                mLastKeyCodeTime = SystemClock.uptimeMillis();
+                return;
+            }
+        } else {
+            if (mLastKeyCode == KeyEvent.KEYCODE_VOLUME_UP &&
+                    keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+                if ((SystemClock.uptimeMillis() - mLastKeyCodeTime) < 750) {
+                    mTriggeredRunnable.run();
+                }
+            }
+        }
+        reset();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
new file mode 100644
index 0000000..f0b2cb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
@@ -0,0 +1,85 @@
+/*
+ * 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.os.Handler;
+
+/**
+ * A dozer is a class that fires a trigger after it falls asleep.  You can occasionally poke it to
+ * wake it up, but it will fall asleep if left untouched.
+ */
+public class DozeTrigger {
+
+    Handler mHandler;
+
+    boolean mIsDozing;
+    boolean mHasTriggered;
+    int mDozeDurationSeconds;
+    Runnable mSleepRunnable;
+
+    // Sleep-runnable
+    Runnable mDozeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mSleepRunnable.run();
+            mIsDozing = false;
+            mHasTriggered = true;
+        }
+    };
+
+    public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
+        mHandler = new Handler();
+        mDozeDurationSeconds = dozeDurationSeconds;
+        mSleepRunnable = sleepRunnable;
+    }
+
+    /** Starts dozing. This also resets the trigger flag. */
+    public void startDozing() {
+        forcePoke();
+        mHasTriggered = false;
+    }
+
+    /** Stops dozing. */
+    public void stopDozing() {
+        mHandler.removeCallbacks(mDozeRunnable);
+        mIsDozing = false;
+    }
+
+    /** Poke this dozer to wake it up for a little bit, if it is dozing. */
+    public void poke() {
+        if (mIsDozing) {
+            forcePoke();
+        }
+    }
+
+    /** Poke this dozer to wake it up for a little bit. */
+    void forcePoke() {
+        mHandler.removeCallbacks(mDozeRunnable);
+        mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
+        mIsDozing = true;
+    }
+
+    /** Returns whether we are dozing or not. */
+    public boolean isDozing() {
+        return mIsDozing;
+    }
+
+    /** Returns whether the trigger has fired at least once. */
+    public boolean hasTriggered() {
+        return mHasTriggered;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 96344d5..4b4cf01 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -17,85 +17,107 @@
 package com.android.systemui.recents;
 
 import android.app.Activity;
-import android.appwidget.AppWidgetHost;
+import android.app.ActivityOptions;
+import android.app.SearchManager;
 import android.appwidget.AppWidgetHostView;
 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.content.SharedPreferences;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Pair;
-import android.view.Gravity;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
+import android.view.ViewStub;
+import android.widget.Toast;
 import com.android.systemui.R;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.SpaceNode;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
 import com.android.systemui.recents.views.RecentsView;
+import com.android.systemui.recents.views.SystemBarScrimViews;
+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 {
-    /* Callbacks to notify when an app package changes */
-    interface RecentsAppWidgetHostCallbacks {
-        public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo);
-    }
-
-    RecentsAppWidgetHostCallbacks mCb;
-
-    public RecentsAppWidgetHost(Context context, int hostId, RecentsAppWidgetHostCallbacks cb) {
-        super(context, hostId);
-        mCb = cb;
-    }
-
-    @Override
-    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
-        mCb.onProviderChanged(appWidgetId, appWidget);
-    }
-}
 
 /* Activity */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
-    FrameLayout mContainerView;
-    RecentsView mRecentsView;
-    View mEmptyView;
-    View mNavBarScrimView;
+        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
+        FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
 
-    AppWidgetHost mAppWidgetHost;
+    RecentsView mRecentsView;
+    SystemBarScrimViews mScrimViews;
+    ViewStub mEmptyViewStub;
+    View mEmptyView;
+    ViewStub mFullscreenOverlayStub;
+    FullscreenTransitionOverlayView mFullScreenOverlayView;
+
+    RecentsConfiguration mConfig;
+
+    RecentsAppWidgetHost mAppWidgetHost;
     AppWidgetProviderInfo mSearchAppWidgetInfo;
     AppWidgetHostView mSearchAppWidgetHostView;
 
     boolean mVisible;
     boolean mTaskLaunched;
 
-    private static Method sPropertyMethod;
-    static {
-        try {
-            Class<?> c = Class.forName("android.view.GLES20Canvas");
-            sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
-            if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-        } catch (NoSuchMethodException e) {
-            e.printStackTrace();
+    // Runnables to finish the Recents activity
+    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
+    FinishRecentsRunnable mFinishWithoutAnimationRunnable = new FinishRecentsRunnable(false);
+    FinishRecentsRunnable mFinishLaunchHomeRunnable;
+
+    /**
+     * A Runnable to finish Recents either with/without a transition, and either by calling finish()
+     * or just launching the specified intent.
+     */
+    class FinishRecentsRunnable implements Runnable {
+        boolean mUseCustomFinishTransition;
+        Intent mLaunchIntent;
+        ActivityOptions mLaunchOpts;
+
+        public FinishRecentsRunnable(boolean withTransition) {
+            mUseCustomFinishTransition = withTransition;
+        }
+
+        /**
+         * Creates a finish runnable that starts the specified intent, using the given
+         * ActivityOptions.
+         */
+        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
+            mLaunchIntent = launchIntent;
+            mLaunchOpts = opts;
+        }
+
+        @Override
+        public void run() {
+            // Mark Recents as no longer visible
+            AlternateRecentsComponent.notifyVisibilityChanged(false);
+            // Finish Recents
+            if (mLaunchIntent != null) {
+                if (mLaunchOpts != null) {
+                    startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT));
+                } else {
+                    startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(),
+                            new UserHandle(UserHandle.USER_CURRENT));
+                }
+            } else {
+                finish();
+                if (mUseCustomFinishTransition) {
+                    overridePendingTransition(R.anim.recents_to_launcher_enter,
+                            R.anim.recents_to_launcher_exit);
+                }
+            }
         }
     }
 
     // Broadcast receiver to handle messages from our RecentsService
-    BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
+    final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -108,8 +130,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 (mFullScreenOverlayView == null ||
+                            !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                        // Otherwise, either finish Recents, or launch Home directly
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
+                                null, mFinishLaunchHomeRunnable, null);
+                        mRecentsView.startExitToHomeAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
                 }
             } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
                 // Try and unfilter and filtered stacks
@@ -119,28 +148,34 @@
                 }
             } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
                 // Try and start the enter animation (or restart it on configuration changed)
-                mRecentsView.startOnEnterAnimation();
+                mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
+                // Call our callback
+                onEnterAnimationTriggered();
+            } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
+                // Refresh the search widget
+                refreshSearchWidget();
             }
         }
     };
 
     // Broadcast receiver to handle messages from the system
-    BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+    final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            finish();
+            mFinishWithoutAnimationRunnable.run();
         }
     };
 
+    // Debug trigger
+    final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
+        @Override
+        public void run() {
+            onDebugModeTriggered();
+        }
+    });
+
     /** 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);
-
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
         ArrayList<TaskStack> stacks = root.getStacks();
@@ -148,24 +183,34 @@
             mRecentsView.setBSP(root);
         }
 
-        // Hide the scrim by default when we enter recents
-        mNavBarScrimView.setVisibility(View.INVISIBLE);
+        // Update the configuration based on the launch intent
+        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);
+        mConfig.launchedWithNoRecentTasks = !root.hasTasks();
+
+        // Show the scrim if we animate into Recents without window transitions
+        mScrimViews.prepareEnterRecentsAnimation();
 
         // Add the default no-recents layout
-        if (stacks.size() == 1 && stacks.get(0).getTaskCount() == 0) {
+        if (mEmptyView == null) {
+            mEmptyView = mEmptyViewStub.inflate();
+        }
+        if (mConfig.launchedWithNoRecentTasks) {
             mEmptyView.setVisibility(View.VISIBLE);
         } else {
             mEmptyView.setVisibility(View.GONE);
         }
-
-        // Dim the background
-        mRecentsView.setBackgroundColor(0x80000000);
     }
 
     /** 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 +218,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 +248,7 @@
                     }
 
                     // Save the app widget id into the settings
-                    config.updateSearchBarAppWidgetId(this, widgetInfo.first);
+                    mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
                     mSearchAppWidgetInfo = widgetInfo.second;
                 }
             }
@@ -213,8 +258,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 +284,24 @@
     /** 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 (mFullScreenOverlayView == null ||
+                    !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                // If we have a focused task, then launch that task
+                if (!mRecentsView.launchFocusedTask()) {
+                    if (mConfig.launchedFromHome) {
+                        // Just start the animation out of recents
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                                null, mFinishLaunchHomeRunnable, null);
+                        mRecentsView.startExitToHomeAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    } else {
+                        // Otherwise, try and launch the first task
+                        if (!mRecentsView.launchFirstTask()) {
+                            // If there are no tasks, then just finish recents
+                            mFinishLaunchHomeRunnable.run();
+                        }
+                    }
                 }
             }
             return true;
@@ -264,38 +323,41 @@
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
-        RecentsConfiguration.reinitialize(this);
+        mConfig = RecentsConfiguration.reinitialize(this);
+
+        // Create the home intent runnable
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
+                ActivityOptions.makeCustomAnimation(this, R.anim.recents_to_launcher_enter,
+                        R.anim.recents_to_launcher_exit));
 
         // Initialize the widget host (the host id is static and does not change)
-        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this);
+        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
 
-        // Create the view hierarchy
-        mRecentsView = new RecentsView(this);
+        // Set the Recents layout
+        setContentView(R.layout.recents);
+        mRecentsView = (RecentsView) findViewById(R.id.recents_view);
         mRecentsView.setCallbacks(this);
-        mRecentsView.setLayoutParams(new FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.MATCH_PARENT,
-                FrameLayout.LayoutParams.MATCH_PARENT));
         mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
-        // Create the empty view
-        LayoutInflater inflater = LayoutInflater.from(this);
-        mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false);
-        mNavBarScrimView = inflater.inflate(R.layout.recents_nav_bar_scrim, mContainerView, false);
-        mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));
-
-        mContainerView = new FrameLayout(this);
-        mContainerView.addView(mRecentsView);
-        mContainerView.addView(mEmptyView);
-        mContainerView.addView(mNavBarScrimView);
-        setContentView(mContainerView);
+        mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
+        mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
+        mScrimViews = new SystemBarScrimViews(this, mConfig);
 
         // Update the recent tasks
         updateRecentsTasks(getIntent());
 
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
+            mFullScreenOverlayView.setCallbacks(this);
+            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Bind the search app widget when we first start up
         bindSearchBarAppWidget();
         // Add the search bar layout
@@ -306,10 +368,10 @@
             onConfigurationChange();
         }
 
-        // XXX: Update the shadows
+        // Private API calls to make the shadows look better
         try {
-            sPropertyMethod.invoke(null, "ambientShadowStrength", String.valueOf(35f));
-            sPropertyMethod.invoke(null, "ambientRatio", String.valueOf(0.5f));
+            Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f));
+            Utilities.setShadowProperty("ambientRatio", String.valueOf(0.5f));
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         } catch (InvocationTargetException e) {
@@ -319,7 +381,9 @@
 
     void onConfigurationChange() {
         // Try and start the enter animation (or restart it on configuration changed)
-        mRecentsView.startOnEnterAnimation();
+        mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
+        // Call our callback
+        onEnterAnimationTriggered();
     }
 
     @Override
@@ -338,11 +402,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) {
+            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Don't attempt to rebind the search bar widget, but just add the search bar layout
         addSearchBarAppWidgetView();
     }
@@ -355,12 +424,6 @@
         }
         super.onStart();
 
-        // Start listening for widget package changes if there is one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
-            mAppWidgetHost.startListening();
-        }
-
         mVisible = true;
     }
 
@@ -387,6 +450,7 @@
         filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
         filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
         filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
+        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
         registerReceiver(mServiceBroadcastReceiver, filter);
 
         // Register the broadcast receiver to handle messages when the screen is turned off
@@ -396,6 +460,11 @@
 
         // Register any broadcast receivers for the task loader
         RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
+
+        // Start listening for widget package changes if there is one bound
+        if (mConfig.searchBarAppWidgetId >= 0) {
+            mAppWidgetHost.startListening(this);
+        }
     }
 
     @Override
@@ -411,6 +480,11 @@
         unregisterReceiver(mServiceBroadcastReceiver);
         unregisterReceiver(mScreenOffReceiver);
         RecentsTaskLoader.getInstance().unregisterReceivers();
+
+        // Stop listening for widget package changes if there was one bound
+        if (mConfig.searchBarAppWidgetId >= 0) {
+            mAppWidgetHost.stopListening();
+        }
     }
 
     @Override
@@ -430,12 +504,6 @@
         }
         super.onStop();
 
-        // Stop listening for widget package changes if there was one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
-            mAppWidgetHost.stopListening();
-        }
-
         mVisible = false;
         mTaskLaunched = false;
     }
@@ -465,63 +533,106 @@
             mRecentsView.focusNextTask(!backward);
             return true;
         }
-
+        // Pass through the debug trigger
+        mDebugTrigger.onKeyEvent(keyCode);
         return super.onKeyDown(keyCode, event);
     }
 
     @Override
+    public void onUserInteraction() {
+        mRecentsView.onUserInteraction();
+    }
+
+    @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 (mFullScreenOverlayView == null ||
+                !mFullScreenOverlayView.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, mFinishLaunchHomeRunnable, null);
+                    mRecentsView.startExitToHomeAnimation(
+                            new ViewAnimation.TaskViewExitContext(exitTrigger));
+                } else {
+                    // Otherwise, try and launch the first task
+                    if (!mRecentsView.launchFirstTask()) {
+                        // If there are no tasks, then just finish recents
+                        mFinishLaunchHomeRunnable.run();
+                    }
+                }
             }
         }
     }
 
-    @Override
-    public void onEnterAnimationTriggered() {
-        // Fade in the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.hasNavBarScrim()) {
-            mNavBarScrimView.setVisibility(View.VISIBLE);
-            mNavBarScrimView.setAlpha(0f);
-            mNavBarScrimView.animate().alpha(1f)
-                    .setStartDelay(config.taskBarEnterAnimDelay)
-                    .setDuration(config.navBarScrimEnterDuration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
-                    .withLayer()
-                    .start();
+    /** Called when debug mode is triggered */
+    public void onDebugModeTriggered() {
+        if (mConfig.developerOptionsEnabled) {
+            SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
+            if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+                // Disable the debug mode
+                settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+            } else {
+                // Enable the debug mode
+                settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+            }
+            Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
+                    ") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
         }
     }
 
+    /** Called when the enter recents animation is triggered. */
+    public void onEnterAnimationTriggered() {
+        // Animate the scrims in
+        mScrimViews.startEnterRecentsAnimation();
+    }
+
+    /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
+
     @Override
-    public void onTaskLaunching(boolean isTaskInStackBounds) {
+    public void onEnterAnimationComplete(boolean canceled) {
+        if (!canceled) {
+            // Reset the full screenshot transition view
+            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+                mFullScreenOverlayView.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();
+    }
+
+    /**** RecentsView.RecentsViewCallbacks Implementation ****/
+
+    @Override
+    public void onExitToHomeAnimationTriggered() {
+        // Animate the scrims out
+        mScrimViews.startExitRecentsAnimation();
+    }
+
+    @Override
+    public void onTaskLaunching() {
         mTaskLaunched = true;
 
-        // Fade out the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (!isTaskInStackBounds && config.hasNavBarScrim()) {
-            mNavBarScrimView.animate().alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(config.taskBarExitAnimDuration)
-                    .setInterpolator(config.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) {
-            // The search provider may have changed, so just delete the old widget and bind it again
-            ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
-            config.updateSearchBarAppWidgetId(this, -1);
-            // Load the widget again
-            bindSearchBarAppWidget();
-            addSearchBarAppWidgetView();
-        }
+    public void onLastTaskRemoved() {
+        mFinishLaunchHomeRunnable.run();
+    }
+
+    /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
+
+    @Override
+    public void refreshSearchWidget() {
+        // Load the Search widget again
+        bindSearchBarAppWidget();
+        addSearchBarAppWidgetView();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
new file mode 100644
index 0000000..3754340
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -0,0 +1,68 @@
+/*
+ * 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.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+
+/** Our special app widget host for the Search widget */
+public class RecentsAppWidgetHost extends AppWidgetHost {
+
+    /* Callbacks to notify when an app package changes */
+    interface RecentsAppWidgetHostCallbacks {
+        public void refreshSearchWidget();
+    }
+
+    Context mContext;
+    RecentsAppWidgetHostCallbacks mCb;
+    RecentsConfiguration mConfig;
+
+    public RecentsAppWidgetHost(Context context, int hostId) {
+        super(context, hostId);
+        mContext = context;
+        mConfig = RecentsConfiguration.getInstance();
+    }
+
+    public void startListening(RecentsAppWidgetHostCallbacks cb) {
+        mCb = cb;
+        super.startListening();
+    }
+
+    @Override
+    public void stopListening() {
+        super.stopListening();
+        // Ensure that we release any references to the callbacks
+        mCb = null;
+        mContext = null;
+    }
+
+    @Override
+    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
+        if (mCb == null) return;
+
+        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        if (appWidgetId > -1 && appWidgetId == mConfig.searchBarAppWidgetId) {
+            // The search provider may have changed, so just delete the old widget and bind it again
+            ssp.unbindSearchAppWidget(this, appWidgetId);
+            // Update the search widget
+            mConfig.updateSearchBarAppWidgetId(mContext, -1);
+            mCb.refreshSearchWidget();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 0cf6ee6..d57f779 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -37,27 +37,40 @@
 
     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;
 
-    public int filteringCurrentViewsMinAnimDuration;
-    public int filteringNewViewsMinAnimDuration;
+    /** Filtering */
+    public int filteringCurrentViewsAnimDuration;
+    public int filteringNewViewsAnimDuration;
 
-    public int taskStackScrollDismissInfoPaneDistance;
+    /** 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 float taskStackWidthPaddingPct;
     public int taskStackTopPaddingPx;
+    public float taskStackWidthPaddingPct;
 
+    /** Task view animation and styles */
+    public int taskViewEnterFromHomeDuration;
+    public int taskViewEnterFromHomeDelay;
+    public int taskViewExitToHomeDuration;
     public int taskViewRemoveAnimDuration;
     public int taskViewRemoveAnimTranslationXPx;
     public int taskViewTranslationZMinPx;
@@ -66,32 +79,57 @@
     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;
+    public int taskBarDismissDozeDelaySeconds;
 
+    /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
-    public boolean launchedFromAltTab;
-    public boolean launchedWithThumbnailAnimation;
+    /** Launch states */
+    public boolean launchedWithAltTab;
+    public boolean launchedWithNoRecentTasks;
+    public boolean launchedFromAppWithThumbnail;
+    public boolean launchedFromAppWithScreenshot;
+    public boolean launchedFromHome;
 
+    /** Dev options */
     public boolean developerOptionsEnabled;
+    public boolean debugModeEnabled;
 
     /** Private constructor */
-    private RecentsConfiguration() {}
+    private RecentsConfiguration(Context context) {
+        // Properties that don't have to be reloaded with each configuration change can be loaded
+        // here.
+
+        // 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);
+
+        // Check if the developer options are enabled
+        ContentResolver cr = context.getContentResolver();
+        developerOptionsEnabled = Settings.Global.getInt(cr,
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+    }
 
     /** Updates the configuration to the current context */
     public static RecentsConfiguration reinitialize(Context context) {
         if (sInstance == null) {
-            sInstance = new RecentsConfiguration();
+            sInstance = new RecentsConfiguration(context);
         }
         sInstance.update(context);
         return sInstance;
@@ -104,37 +142,53 @@
 
     /** Updates the state, given the specified context */
     void update(Context context) {
+        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
         Resources res = context.getResources();
         DisplayMetrics dm = res.getDisplayMetrics();
         mDisplayMetrics = dm;
 
+        // Debug mode
+        debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+
+        // Animations
+        animationPxMovementPerSecond =
+                res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
+
+        // Filtering
+        filteringCurrentViewsAnimDuration =
+                res.getInteger(R.integer.recents_filter_animate_current_views_duration);
+        filteringNewViewsAnimDuration =
+                res.getInteger(R.integer.recents_filter_animate_new_views_duration);
+
+        // 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);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
-                    Console.AnsiGreen);
-        }
 
-        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
-        animationPxMovementPerSecond =
-                res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
-        filteringCurrentViewsMinAnimDuration =
-                res.getInteger(R.integer.recents_filter_animate_current_views_min_duration);
-        filteringNewViewsMinAnimDuration =
-                res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
+        // Search bar
+        searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
 
-        taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
-        taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
+        // Update the search widget id
+        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);
+        taskViewExitToHomeDuration =
+                res.getInteger(R.integer.recents_animate_task_exit_to_home_duration);
         taskViewRemoveAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_view_remove_duration);
         taskViewRemoveAnimTranslationXPx =
@@ -148,12 +202,9 @@
         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 =
-                res.getColor(R.color.recents_task_bar_default_text_color);
         taskBarViewLightTextColor =
                 res.getColor(R.color.recents_task_bar_light_text_color);
         taskBarViewDarkTextColor =
@@ -161,31 +212,25 @@
         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 =
                 res.getInteger(R.integer.recents_animate_task_bar_enter_delay);
         taskBarExitAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_exit_duration);
+        taskBarDismissDozeDelaySeconds =
+                res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
 
+        // 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 +249,11 @@
     /** Called when the configuration has changed, and we want to reset any configuration specific
      * members. */
     public void updateOnConfigurationChange() {
-        launchedFromAltTab = false;
-        launchedWithThumbnailAnimation = false;
+        launchedWithAltTab = false;
+        launchedWithNoRecentTasks = false;
+        launchedFromAppWithThumbnail = false;
+        launchedFromAppWithScreenshot = false;
+        launchedFromHome = false;
     }
 
     /** Returns whether the search bar app widget exists. */
@@ -213,9 +261,26 @@
         return searchBarAppWidgetId >= 0;
     }
 
+    /** Returns whether the status bar scrim should be animated when shown for the first time. */
+    public boolean shouldAnimateStatusBarScrim() {
+        return launchedFromHome;
+    }
+
+    /** Returns whether the status bar scrim should be visible. */
+    public boolean hasStatusBarScrim() {
+        return !launchedWithNoRecentTasks;
+    }
+
+    /** Returns whether the nav bar scrim should be animated when shown for the first time. */
+    public boolean shouldAnimateNavBarScrim() {
+        return true;
+    }
+
     /** Returns whether the nav bar scrim should be visible. */
     public boolean hasNavBarScrim() {
-        return !transposeRecentsLayoutWithOrientation || !isLandscape;
+        // Only show the scrim if we have recent tasks, and if the nav bar is not transposed
+        return !launchedWithNoRecentTasks &&
+                (!transposeRecentsLayoutWithOrientation || !isLandscape);
     }
 
     /**
@@ -257,15 +322,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/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsPackageMonitor.java
deleted file mode 100644
index 8bcc7f5..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsPackageMonitor.java
+++ /dev/null
@@ -1,119 +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.systemui.recents;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Looper;
-import com.android.internal.content.PackageMonitor;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * The package monitor listens for changes from PackageManager to update the contents of the Recents
- * list.
- */
-public class RecentsPackageMonitor extends PackageMonitor {
-    public interface PackageCallbacks {
-        public void onComponentRemoved(Set<ComponentName> cns);
-    }
-
-    PackageCallbacks mCb;
-    List<ActivityManager.RecentTaskInfo> mTasks;
-    SystemServicesProxy mSsp;
-    boolean mRegistered;
-
-    public RecentsPackageMonitor(Context context) {
-        mSsp = new SystemServicesProxy(context);
-    }
-
-    /** Registers the broadcast receivers with the specified callbacks. */
-    public void register(Context context, PackageCallbacks cb) {
-        mCb = cb;
-        if (!mRegistered) {
-            register(context, Looper.getMainLooper(), false);
-            mRegistered = true;
-        }
-    }
-
-    /** Unregisters the broadcast receivers. */
-    @Override
-    public void unregister() {
-        if (mRegistered) {
-            super.unregister();
-            mRegistered = false;
-        }
-        mTasks.clear();
-    }
-
-    /** Sets the list of tasks to match against package broadcast changes. */
-    void setTasks(List<ActivityManager.RecentTaskInfo> tasks) {
-        mTasks = tasks;
-    }
-
-    @Override
-    public void onPackageRemoved(String packageName, int uid) {
-        if (mCb == null) return;
-
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
-        for (ActivityManager.RecentTaskInfo t : mTasks) {
-            ComponentName cn = t.baseIntent.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                componentsToRemove.add(cn);
-            }
-        }
-        // Notify our callbacks that the components no longer exist
-        mCb.onComponentRemoved(componentsToRemove);
-    }
-
-    @Override
-    public boolean onPackageChanged(String packageName, int uid, String[] components) {
-        onPackageModified(packageName);
-        return true;
-    }
-
-    @Override
-    public void onPackageModified(String packageName) {
-        if (mCb == null) return;
-
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>();
-        HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
-        for (ActivityManager.RecentTaskInfo t : mTasks) {
-            ComponentName cn = t.baseIntent.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (componentsKnownToExist.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                if (mSsp.getActivityInfo(cn) != null) {
-                    componentsKnownToExist.add(cn);
-                } else {
-                    componentsToRemove.add(cn);
-                }
-            }
-        }
-        // Notify our callbacks that the components no longer exist
-        mCb.onComponentRemoved(componentsToRemove);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 113efe3..b6895d1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -26,9 +26,11 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewTransform;
 
 import java.lang.ref.WeakReference;
@@ -69,6 +71,7 @@
                 // Create a dummy task stack & compute the rect for the thumbnail to animate to
                 TaskStack stack = new TaskStack(context);
                 TaskStackView tsv = new TaskStackView(context, stack);
+                TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
                 Bundle replyData = new Bundle();
                 TaskViewTransform transform;
 
@@ -84,7 +87,7 @@
                 tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
                         systemInsets.top - systemInsets.bottom, 0, 0);
                 tsv.setStackScrollToInitialState();
-                transform = tsv.getStackTransform(0, tsv.getStackScroll());
+                transform = algo.getStackTransform(0, tsv.getStackScroll());
                 transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
                 replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT,
                         new Rect(transform.rect));
@@ -94,7 +97,7 @@
                 tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
                         systemInsets.top - systemInsets.bottom, 0, 0);
                 tsv.setStackScrollToInitialState();
-                transform = tsv.getStackTransform(1, tsv.getStackScroll());
+                transform = algo.getStackTransform(1, tsv.getStackScroll());
                 transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
                 replyData.putParcelable(AlternateRecentsComponent.KEY_TWO_TASK_STACK_RECT,
                         new Rect(transform.rect));
@@ -104,7 +107,7 @@
                 tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
                         systemInsets.top - systemInsets.bottom, 0, 0);
                 tsv.setStackScrollToInitialState();
-                transform = tsv.getStackTransform(2, tsv.getStackScroll());
+                transform = algo.getStackTransform(2, tsv.getStackScroll());
                 transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
                 replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT,
                         new Rect(transform.rect));
@@ -134,10 +137,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
deleted file mode 100644
index 4685186..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ /dev/null
@@ -1,653 +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.systemui.recents;
-
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.UserHandle;
-import android.util.LruCache;
-import android.util.Pair;
-import com.android.systemui.recents.model.SpaceNode;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-
-/** A bitmap load queue */
-class TaskResourceLoadQueue {
-    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-    ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
-            new ConcurrentHashMap<Task.TaskKey, Boolean>();
-
-    static final Boolean sFalse = new Boolean(false);
-
-    /** Adds a new task to the load queue */
-    void addTask(Task t, boolean forceLoad) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
-        }
-        if (!mQueue.contains(t)) {
-            mQueue.add(t);
-        }
-        if (forceLoad) {
-            mForceLoadSet.put(t.key, new Boolean(true));
-        }
-        synchronized(this) {
-            notifyAll();
-        }
-    }
-
-    /**
-     * Retrieves the next task from the load queue, as well as whether we want that task to be
-     * force reloaded.
-     */
-    Pair<Task, Boolean> nextTask() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
-        }
-        Task task = mQueue.poll();
-        Boolean forceLoadTask = null;
-        if (task != null) {
-            forceLoadTask = mForceLoadSet.remove(task.key);
-        }
-        if (forceLoadTask == null) {
-            forceLoadTask = sFalse;
-        }
-        return new Pair<Task, Boolean>(task, forceLoadTask);
-    }
-
-    /** Removes a task from the load queue */
-    void removeTask(Task t) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
-        }
-        mQueue.remove(t);
-        mForceLoadSet.remove(t.key);
-    }
-
-    /** Clears all the tasks from the load queue */
-    void clearTasks() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
-        }
-        mQueue.clear();
-        mForceLoadSet.clear();
-    }
-
-    /** Returns whether the load queue is empty */
-    boolean isEmpty() {
-        return mQueue.isEmpty();
-    }
-}
-
-/* Task resource loader */
-class TaskResourceLoader implements Runnable {
-    Context mContext;
-    HandlerThread mLoadThread;
-    Handler mLoadThreadHandler;
-    Handler mMainThreadHandler;
-
-    SystemServicesProxy mSystemServicesProxy;
-    TaskResourceLoadQueue mLoadQueue;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
-
-    boolean mCancelled;
-    boolean mWaitingOnLoadQueue;
-
-    /** Constructor, creates a new loading thread that loads task resources in the background */
-    public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
-                              DrawableLruCache applicationIconCache,
-                              BitmapLruCache thumbnailCache) {
-        mLoadQueue = loadQueue;
-        mApplicationIconCache = applicationIconCache;
-        mThumbnailCache = thumbnailCache;
-        mMainThreadHandler = new Handler();
-        mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
-        mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
-        mLoadThread.start();
-        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
-        mLoadThreadHandler.post(this);
-    }
-
-    /** Restarts the loader thread */
-    void start(Context context) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
-        }
-        mContext = context;
-        mCancelled = false;
-        mSystemServicesProxy = new SystemServicesProxy(context);
-        // Notify the load thread to start loading
-        synchronized(mLoadThread) {
-            mLoadThread.notifyAll();
-        }
-    }
-
-    /** Requests the loader thread to stop after the current iteration */
-    void stop() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
-        }
-        // Mark as cancelled for the thread to pick up
-        mCancelled = true;
-        mSystemServicesProxy = null;
-        // If we are waiting for the load queue for more tasks, then we can just reset the
-        // Context now, since nothing is using it
-        if (mWaitingOnLoadQueue) {
-            mContext = null;
-        }
-    }
-
-    @Override
-    public void run() {
-        while (true) {
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.TaskDataLoader,
-                        "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
-            }
-            if (mCancelled) {
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.TaskDataLoader,
-                            "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
-                }
-                // We have to unset the context here, since the background thread may be using it
-                // when we call stop()
-                mContext = null;
-                // If we are cancelled, then wait until we are started again
-                synchronized(mLoadThread) {
-                    try {
-                        if (Console.Enabled) {
-                            Console.log(Constants.Log.App.TaskDataLoader,
-                                    "[TaskResourceLoader|waitOnLoadThreadCancelled]");
-                        }
-                        mLoadThread.wait();
-                    } catch (InterruptedException ie) {
-                        ie.printStackTrace();
-                    }
-                }
-            } else {
-                SystemServicesProxy ssp = mSystemServicesProxy;
-
-                // Load the next item from the queue
-                Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
-                final Task t = nextTaskData.first;
-                final boolean forceLoadTask = nextTaskData.second;
-                if (t != null) {
-                    Drawable loadIcon = mApplicationIconCache.get(t.key);
-                    Bitmap loadThumbnail = mThumbnailCache.get(t.key);
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.App.TaskDataLoader,
-                                "  [TaskResourceLoader|load]",
-                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
-                                        " forceLoad: " + forceLoadTask);
-                    }
-                    // Load the application icon
-                    if (loadIcon == null || forceLoadTask) {
-                        ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
-                                t.userId);
-                        Drawable icon = ssp.getActivityIcon(info, t.userId);
-                        if (!mCancelled) {
-                            if (icon != null) {
-                                if (Console.Enabled) {
-                                    Console.log(Constants.Log.App.TaskDataLoader,
-                                            "    [TaskResourceLoader|loadIcon]", icon);
-                                }
-                                loadIcon = icon;
-                                mApplicationIconCache.put(t.key, icon);
-                            }
-                        }
-                    }
-                    // Load the thumbnail
-                    if (loadThumbnail == null || forceLoadTask) {
-                        Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
-                        if (!mCancelled) {
-                            if (thumbnail != null) {
-                                if (Console.Enabled) {
-                                    Console.log(Constants.Log.App.TaskDataLoader,
-                                            "    [TaskResourceLoader|loadThumbnail]", thumbnail);
-                                }
-                                thumbnail.setHasAlpha(false);
-                                loadThumbnail = thumbnail;
-                                mThumbnailCache.put(t.key, thumbnail);
-                            } else {
-                                Console.logError(mContext,
-                                        "Failed to load task top thumbnail for: " +
-                                                t.key.baseIntent.getComponent().getPackageName());
-                            }
-                        }
-                    }
-                    if (!mCancelled) {
-                        // Notify that the task data has changed
-                        final Drawable newIcon = loadIcon;
-                        final Bitmap newThumbnail = loadThumbnail;
-                        mMainThreadHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                t.notifyTaskDataLoaded(newThumbnail, newIcon, forceLoadTask);
-                            }
-                        });
-                    }
-                }
-
-                // If there are no other items in the list, then just wait until something is added
-                if (!mCancelled && mLoadQueue.isEmpty()) {
-                    synchronized(mLoadQueue) {
-                        try {
-                            if (Console.Enabled) {
-                                Console.log(Constants.Log.App.TaskDataLoader,
-                                        "[TaskResourceLoader|waitOnLoadQueue]");
-                            }
-                            mWaitingOnLoadQueue = true;
-                            mLoadQueue.wait();
-                            mWaitingOnLoadQueue = false;
-                        } catch (InterruptedException ie) {
-                            ie.printStackTrace();
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-/**
- * The drawable cache.  By using the Task's key, we can prevent holding onto a reference to the Task
- * resource data, while keeping the cache data in memory where necessary.
- */
-class DrawableLruCache extends LruCache<Task.TaskKey, Drawable> {
-    public DrawableLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-
-    @Override
-    protected int sizeOf(Task.TaskKey t, Drawable d) {
-        // The cache size will be measured in kilobytes rather than number of items
-        // NOTE: this isn't actually correct, as the icon may be smaller
-        int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
-        return maxBytes / 1024;
-    }
-}
-
-/**
- * The bitmap cache.  By using the Task's key, we can prevent holding onto a reference to the Task
- * resource data, while keeping the cache data in memory where necessary.
- */
-class BitmapLruCache extends LruCache<Task.TaskKey, Bitmap> {
-    public BitmapLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-
-    @Override
-    protected int sizeOf(Task.TaskKey t, Bitmap bitmap) {
-        // The cache size will be measured in kilobytes rather than number of items
-        return bitmap.getAllocationByteCount() / 1024;
-    }
-}
-
-/* Recents task loader
- * NOTE: We should not hold any references to a Context from a static instance */
-public class RecentsTaskLoader {
-    static RecentsTaskLoader sInstance;
-
-    SystemServicesProxy mSystemServicesProxy;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
-    TaskResourceLoadQueue mLoadQueue;
-    TaskResourceLoader mLoader;
-
-    RecentsPackageMonitor mPackageMonitor;
-
-    int mMaxThumbnailCacheSize;
-    int mMaxIconCacheSize;
-
-    BitmapDrawable mDefaultApplicationIcon;
-    Bitmap mDefaultThumbnail;
-
-    /** Private Constructor */
-    private RecentsTaskLoader(Context context) {
-        // Calculate the cache sizes, we just use a reasonable number here similar to those
-        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
-        // for icons.
-        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
-        mMaxThumbnailCacheSize = maxMemory / 8;
-        mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
-        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
-                mMaxIconCacheSize;
-        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
-                mMaxThumbnailCacheSize;
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
-                    " iconCache: " + iconCacheSize);
-        }
-
-        // Initialize the proxy, cache and loaders
-        mSystemServicesProxy = new SystemServicesProxy(context);
-        mPackageMonitor = new RecentsPackageMonitor(context);
-        mLoadQueue = new TaskResourceLoadQueue();
-        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
-        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
-        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache);
-
-        // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        icon.eraseColor(0x00000000);
-        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        mDefaultThumbnail.eraseColor(0x00000000);
-        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|defaultBitmaps]",
-                    "icon: " + mDefaultApplicationIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
-        }
-    }
-
-    /** Initializes the recents task loader */
-    public static RecentsTaskLoader initialize(Context context) {
-        if (sInstance == null) {
-            sInstance = new RecentsTaskLoader(context);
-        }
-        return sInstance;
-    }
-
-    /** Returns the current recents task loader */
-    public static RecentsTaskLoader getInstance() {
-        return sInstance;
-    }
-
-    /** Returns the system services proxy */
-    public SystemServicesProxy getSystemServicesProxy() {
-        return mSystemServicesProxy;
-    }
-
-    private List<ActivityManager.RecentTaskInfo> getRecentTasks() {
-        long t1 = System.currentTimeMillis();
-
-        SystemServicesProxy ssp = mSystemServicesProxy;
-        List<ActivityManager.RecentTaskInfo> tasks =
-                ssp.getRecentTasks(25, UserHandle.CURRENT.getIdentifier());
-        Collections.reverse(tasks);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TimeSystemCalls,
-                    "[RecentsTaskLoader|getRecentTasks]",
-                    "" + (System.currentTimeMillis() - t1) + "ms");
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|tasks]", "" + tasks.size());
-        }
-
-        return tasks;
-    }
-
-    /** Reload the set of recent tasks */
-    SpaceNode reload(Context context, int preloadCount) {
-        long t1 = System.currentTimeMillis();
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
-        }
-        Resources res = context.getResources();
-        ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
-        TaskStack stack = new TaskStack(context);
-        SpaceNode root = new SpaceNode(context);
-        root.setStack(stack);
-
-        // Get the recent tasks
-        SystemServicesProxy ssp = mSystemServicesProxy;
-        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks();
-
-        // Add each task to the task stack
-        t1 = System.currentTimeMillis();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo t = tasks.get(i);
-            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
-            if (info == null) continue;
-
-            ActivityManager.TaskDescription av = t.taskDescription;
-            String activityLabel = null;
-            BitmapDrawable activityIcon = null;
-            int activityColor = 0;
-            if (av != null) {
-                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
-                activityIcon = (av.getIcon() != null) ? new BitmapDrawable(res, av.getIcon()) : null;
-                activityColor = av.getPrimaryColor();
-            } else {
-                activityLabel = ssp.getActivityLabel(info);
-            }
-            boolean isForemostTask = (i == (taskCount - 1));
-
-            // Create a new task
-            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
-                    activityIcon, activityColor, t.userId);
-
-            // Preload the specified number of apps
-            if (i >= (taskCount - preloadCount)) {
-                if (Console.Enabled) {
-                    Console.log(Constants.Log.App.TaskDataLoader,
-                            "[RecentsTaskLoader|preloadTask]",
-                            "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
-                }
-
-                // Load the icon (if possible and not the foremost task, from the cache)
-                if (!isForemostTask) {
-                    task.applicationIcon = mApplicationIconCache.get(task.key);
-                    if (task.applicationIcon != null) {
-                        // Even though we get things from the cache, we should update them
-                        // if they've changed in the bg
-                        tasksToForceLoad.add(task);
-                    }
-                }
-                if (task.applicationIcon == null) {
-                    task.applicationIcon = ssp.getActivityIcon(info, task.userId);
-                    if (task.applicationIcon != null) {
-                        mApplicationIconCache.put(task.key, task.applicationIcon);
-                    } else {
-                        task.applicationIcon = mDefaultApplicationIcon;
-                    }
-                }
-
-                // 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) {
-                        // Even though we get things from the cache, we should update them if
-                        // they've changed in the bg
-                        tasksToForceLoad.add(task);
-                    }
-                }
-                if (task.thumbnail == null) {
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.App.TaskDataLoader,
-                                "[RecentsTaskLoader|loadingTaskThumbnail]");
-                    }
-                    task.thumbnail = ssp.getTaskThumbnail(task.key.id);
-                    if (task.thumbnail != null) {
-                        task.thumbnail.setHasAlpha(false);
-                        mThumbnailCache.put(task.key, task.thumbnail);
-                    } else {
-                        task.thumbnail = mDefaultThumbnail;
-                    }
-                }
-            }
-
-            // Add the task to the stack
-            if (Console.Enabled) {
-                Console.log(Constants.Log.App.TaskDataLoader,
-                        "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
-            }
-            stack.addTask(task);
-        }
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TimeSystemCalls,
-                    "[RecentsTaskLoader|getAllTaskTopThumbnail]",
-                    "" + (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);
-
-        // Add all the tasks that we are force/re-loading
-        for (Task t : tasksToForceLoad) {
-            mLoadQueue.addTask(t, true);
-        }
-
-        // Update the package monitor with the list of packages to listen for
-        mPackageMonitor.setTasks(tasks);
-
-        return root;
-    }
-
-    /** Acquires the task resource data from the pool. */
-    public void loadTaskData(Task t) {
-        Drawable applicationIcon = mApplicationIconCache.get(t.key);
-        Bitmap thumbnail = mThumbnailCache.get(t.key);
-
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
-                    t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
-                            " thumbnailCacheSize: " + mThumbnailCache.size());
-        }
-
-        boolean requiresLoad = false;
-        if (applicationIcon == null) {
-            applicationIcon = mDefaultApplicationIcon;
-            requiresLoad = true;
-        }
-        if (thumbnail == null) {
-            thumbnail = mDefaultThumbnail;
-            requiresLoad = true;
-        }
-        if (requiresLoad) {
-            mLoadQueue.addTask(t, false);
-        }
-        t.notifyTaskDataLoaded(thumbnail, applicationIcon, false);
-    }
-
-    /** Releases the task resource data back into the pool. */
-    public void unloadTaskData(Task t) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|unloadTask]", t +
-                    " thumbnailCacheSize: " + mThumbnailCache.size());
-        }
-
-        mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
-    }
-
-    /** Completely removes the resource data from the pool. */
-    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader,
-                    "[RecentsTaskLoader|deleteTask]", t);
-        }
-
-        mLoadQueue.removeTask(t);
-        mThumbnailCache.remove(t.key);
-        mApplicationIconCache.remove(t.key);
-        if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
-        }
-    }
-
-    /** Stops the task loader and clears all pending tasks */
-    void stopLoader() {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
-        }
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
-    /** Registers any broadcast receivers. */
-    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
-        // Register the broadcast receiver to handle messages related to packages being added/removed
-        mPackageMonitor.register(context, cb);
-    }
-
-    /** Unregisters any broadcast receivers. */
-    public void unregisterReceivers() {
-        mPackageMonitor.unregister();
-    }
-
-    /**
-     * Handles signals from the system, trimming memory when requested to prevent us from running
-     * out of memory.
-     */
-    void onTrimMemory(int level) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
-                    Console.trimMemoryLevelToString(level));
-        }
-
-        switch (level) {
-            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
-                // Stop the loader immediately when the UI is no longer visible
-                stopLoader();
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
-                // We are leaving recents, so trim the data a bit
-                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
-                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                // We are going to be low on memory
-                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
-                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                // We are low on memory, so release everything
-                mThumbnailCache.evictAll();
-                mApplicationIconCache.evictAll();
-                break;
-            default:
-                break;
-        }
-    }
-}
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..d525546
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+
+/**
+ * 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;
+    ArrayList<Runnable> mFirstIncRunnables = new ArrayList<Runnable>();
+    ArrayList<Runnable> mLastDecRunnables = new ArrayList<Runnable>();
+    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;
+        if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);
+        if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);
+        mErrorRunnable = errorRunanable;
+    }
+
+    /** Increments the ref count */
+    public void increment() {
+        if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {
+            int numRunnables = mFirstIncRunnables.size();
+            for (int i = 0; i < numRunnables; i++) {
+                mFirstIncRunnables.get(i).run();
+            }
+        }
+        mCount++;
+    }
+
+    /** Convenience method to increment this trigger as a runnable */
+    public Runnable incrementAsRunnable() {
+        return mIncrementRunnable;
+    }
+
+    /** Adds a runnable to the last-decrement runnables list. */
+    public void addLastDecrementRunnable(Runnable r) {
+        mLastDecRunnables.add(r);
+    }
+
+    /** Decrements the ref count */
+    public void decrement() {
+        mCount--;
+        if (mCount == 0 && !mLastDecRunnables.isEmpty()) {
+            int numRunnables = mLastDecRunnables.size();
+            for (int i = 0; i < numRunnables; i++) {
+                mLastDecRunnables.get(i).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..f7f86c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -30,13 +30,23 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 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.Log;
 import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -48,16 +58,22 @@
  * a point of injection when testing UI.
  */
 public class SystemServicesProxy {
+    final static String TAG = "SystemServicesProxy";
+
     ActivityManager mAm;
     AppWidgetManager mAwm;
     PackageManager mPm;
     IPackageManager mIpm;
     UserManager mUm;
     SearchManager mSm;
+    WindowManager mWm;
+    Display mDisplay;
     String mRecentsPackage;
     ComponentName mAssistComponent;
 
     Bitmap mDummyIcon;
+    Paint mBgProtectionPaint;
+    Canvas mBgProtectionCanvas;
 
     /** Private constructor */
     public SystemServicesProxy(Context context) {
@@ -67,8 +83,16 @@
         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();
 
+        // Create the protection paints
+        mBgProtectionPaint = new Paint();
+        mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+        mBgProtectionPaint.setColor(0xFFffffff);
+        mBgProtectionCanvas = new Canvas();
+
         // Resolve the assist intent
         Intent assist = mSm.getAssistIntent(context, false);
         if (assist != null) {
@@ -184,7 +208,20 @@
             return thumbnail;
         }
 
-        return mAm.getTaskTopThumbnail(taskId);
+        Bitmap thumbnail = mAm.getTaskTopThumbnail(taskId);
+        if (thumbnail != null) {
+            // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
+            // left pixel, then assume the whole thumbnail is transparent. Generally, proper
+            // screenshots are always composed onto a bitmap that has no alpha.
+            if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) {
+                mBgProtectionCanvas.setBitmap(thumbnail);
+                mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(),
+                        mBgProtectionPaint);
+                mBgProtectionCanvas.setBitmap(null);
+                Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
+            }
+        }
+        return thumbnail;
     }
 
     /** Moves a task to the front with the specified activity options */
@@ -269,6 +306,13 @@
         }
 
         Drawable icon = info.loadIcon(mPm);
+        return getBadgedIcon(icon, userId);
+    }
+
+    /**
+     * Returns the given icon for a user, badging if necessary.
+     */
+    public Drawable getBadgedIcon(Drawable icon, int userId) {
         if (userId != UserHandle.myUserId()) {
             icon = mUm.getBadgedDrawableForUser(icon, new UserHandle(userId));
         }
@@ -325,4 +369,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/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
index 46e6ee9..6d6376f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -20,8 +20,26 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 /* Common code */
 public class Utilities {
+
+    // Reflection methods for altering shadows
+    private static Method sPropertyMethod;
+    static {
+        try {
+            Class<?> c = Class.forName("android.view.GLES20Canvas");
+            sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
+            if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        }
+    }
+
     /**
      * Calculates a consistent animation duration (ms) for all animations depending on the movement
      * of the object being animated.
@@ -50,20 +68,26 @@
     }
 
     /** Calculates the luminance-preserved greyscale of a given color. */
-    private static int colorToGreyscale(int color) {
+    public static int colorToGreyscale(int color) {
         return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
                 0.0722f * Color.blue(color));
     }
 
     /** Returns the ideal color to draw on top of a specified background color. */
-    public static int getIdealColorForBackgroundColor(int color, int lightRes, int darkRes) {
-        int greyscale = colorToGreyscale(color);
+    public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
+                                                               int darkRes) {
         return (greyscale < 128) ? lightRes : darkRes;
     }
     /** Returns the ideal drawable to draw on top of a specified background color. */
-    public static Drawable getIdealResourceForBackgroundColor(int color, Drawable lightRes,
-                                                           Drawable darkRes) {
-        int greyscale = colorToGreyscale(color);
+    public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
+                                                                       Drawable lightRes,
+                                                                       Drawable darkRes) {
         return (greyscale < 128) ? lightRes : darkRes;
     }
+
+    /** Sets some private shadow properties. */
+    public static void setShadowProperty(String property, String value)
+            throws IllegalAccessException, InvocationTargetException {
+        sPropertyMethod.invoke(null, property, value);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
new file mode 100644
index 0000000..1344729
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import android.graphics.Bitmap;
+
+/**
+ * The Bitmap LRU cache.
+ */
+class BitmapLruCache extends KeyStoreLruCache<Bitmap> {
+    public BitmapLruCache(int cacheSize) {
+        super(cacheSize);
+    }
+
+    @Override
+    protected int computeSize(Bitmap b) {
+        // The cache size will be measured in kilobytes rather than number of items
+        return b.getAllocationByteCount() / 1024;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
new file mode 100644
index 0000000..61d19da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
@@ -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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * The Drawable LRU cache.
+ */
+class DrawableLruCache extends KeyStoreLruCache<Drawable> {
+    public DrawableLruCache(int cacheSize) {
+        super(cacheSize);
+    }
+
+    @Override
+    protected int computeSize(Drawable d) {
+        // The cache size will be measured in kilobytes rather than number of items
+        // NOTE: this isn't actually correct, as the icon may be smaller
+        int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
+        return maxBytes / 1024;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
new file mode 100644
index 0000000..3ccca9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
@@ -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.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.util.LruCache;
+
+import java.util.HashMap;
+
+/**
+ * An LRU cache that support querying the keys as well as values. By using the Task's key, we can
+ * prevent holding onto a reference to the Task resource data, while keeping the cache data in
+ * memory where necessary.
+ */
+public class KeyStoreLruCache<V> {
+    // We keep a set of keys that are associated with the LRU cache, so that we can find out
+    // information about the Task that was previously in the cache.
+    HashMap<Task.TaskKey, Task.TaskKey> mKeys = new HashMap<Task.TaskKey, Task.TaskKey>();
+    // The cache implementation
+    LruCache<Task.TaskKey, V> mCache;
+
+    public KeyStoreLruCache(int cacheSize) {
+        mCache = new LruCache<Task.TaskKey, V>(cacheSize) {
+            @Override
+            protected int sizeOf(Task.TaskKey t, V v) {
+                return computeSize(v);
+            }
+
+            @Override
+            protected void entryRemoved(boolean evicted, Task.TaskKey key, V oldV, V newV) {
+                mKeys.remove(key);
+            }
+        };
+    }
+
+    /** Computes the size of a value. */
+    protected int computeSize(V value) {
+        return 0;
+    }
+
+    /** Gets a specific entry in the cache. */
+    final V get(Task.TaskKey key) {
+        return mCache.get(key);
+    }
+
+    /**
+     * Returns the value only if the last active time of the key currently in the lru cache is
+     * greater than or equal to the last active time of the key specified.
+     */
+    final V getCheckLastActiveTime(Task.TaskKey key) {
+        Task.TaskKey lruKey = mKeys.get(key);
+        if (lruKey != null && (lruKey.lastActiveTime < key.lastActiveTime)) {
+            // The task has changed (been made active since the last time it was put into the
+            // LRU cache) so invalidate that item in the cache
+            remove(lruKey);
+            return null;
+        }
+        // Either the task does not exist in the cache, or the last active time is the same as
+        // the key specified
+        return mCache.get(key);
+    }
+
+    /** Gets the previous task key that matches the specified key. */
+    final Task.TaskKey getKey(Task.TaskKey key) {
+        return mKeys.get(key);
+    }
+
+    /** Puts an entry in the cache for a specific key. */
+    final void put(Task.TaskKey key, V value) {
+        mCache.put(key, value);
+        if (mKeys.containsKey(key)) {
+            mKeys.get(key).updateLastActiveTime(key.lastActiveTime);
+        } else {
+            mKeys.put(key, key);
+        }
+    }
+
+    /** Removes a cache entry for a specific key. */
+    final void remove(Task.TaskKey key) {
+        mCache.remove(key);
+        mKeys.remove(key);
+    }
+
+    /** Removes all the entries in the cache. */
+    final void evictAll() {
+        mCache.evictAll();
+        mKeys.clear();
+    }
+
+    /** Returns the size of the cache. */
+    final int size() {
+        return mCache.size();
+    }
+
+    /** Trims the cache to a specific size */
+    final void trimToSize(int cacheSize) {
+        mCache.resize(cacheSize);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
new file mode 100644
index 0000000..cbe39e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -0,0 +1,120 @@
+/*
+ * 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.model;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Looper;
+import com.android.internal.content.PackageMonitor;
+import com.android.systemui.recents.SystemServicesProxy;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The package monitor listens for changes from PackageManager to update the contents of the Recents
+ * list.
+ */
+public class RecentsPackageMonitor extends PackageMonitor {
+    public interface PackageCallbacks {
+        public void onComponentRemoved(Set<ComponentName> cns);
+    }
+
+    PackageCallbacks mCb;
+    List<ActivityManager.RecentTaskInfo> mTasks;
+    SystemServicesProxy mSystemServicesProxy;
+
+    /** Registers the broadcast receivers with the specified callbacks. */
+    public void register(Context context, PackageCallbacks cb) {
+        mSystemServicesProxy = new SystemServicesProxy(context);
+        mCb = cb;
+        try {
+            register(context, Looper.getMainLooper(), false);
+        } catch (IllegalStateException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Unregisters the broadcast receivers. */
+    @Override
+    public void unregister() {
+        try {
+            super.unregister();
+        } catch (IllegalStateException e) {
+            e.printStackTrace();
+        }
+        mSystemServicesProxy = null;
+        mCb = null;
+        mTasks.clear();
+    }
+
+    /** Sets the list of tasks to match against package broadcast changes. */
+    void setTasks(List<ActivityManager.RecentTaskInfo> tasks) {
+        mTasks = tasks;
+    }
+
+    @Override
+    public void onPackageRemoved(String packageName, int uid) {
+        if (mCb == null) return;
+
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
+        for (ActivityManager.RecentTaskInfo t : mTasks) {
+            ComponentName cn = t.baseIntent.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                componentsToRemove.add(cn);
+            }
+        }
+        // Notify our callbacks that the components no longer exist
+        mCb.onComponentRemoved(componentsToRemove);
+    }
+
+    @Override
+    public boolean onPackageChanged(String packageName, int uid, String[] components) {
+        onPackageModified(packageName);
+        return true;
+    }
+
+    @Override
+    public void onPackageModified(String packageName) {
+        if (mCb == null) return;
+
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>();
+        HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>();
+        for (ActivityManager.RecentTaskInfo t : mTasks) {
+            ComponentName cn = t.baseIntent.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                if (componentsKnownToExist.contains(cn)) {
+                    // If we know that the component still exists in the package, then skip
+                    continue;
+                }
+                if (mSystemServicesProxy.getActivityInfo(cn) != null) {
+                    componentsKnownToExist.add(cn);
+                } else {
+                    componentsToRemove.add(cn);
+                }
+            }
+        }
+        // Notify our callbacks that the components no longer exist
+        mCb.onComponentRemoved(componentsToRemove);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
new file mode 100644
index 0000000..15f4a76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -0,0 +1,616 @@
+/*
+ * 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.model;
+
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.util.Pair;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.SystemServicesProxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+
+/** A bitmap load queue */
+class TaskResourceLoadQueue {
+    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
+    ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
+            new ConcurrentHashMap<Task.TaskKey, Boolean>();
+
+    static final Boolean sFalse = new Boolean(false);
+
+    /** Adds a new task to the load queue */
+    void addTask(Task t, boolean forceLoad) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
+        }
+        if (!mQueue.contains(t)) {
+            mQueue.add(t);
+        }
+        if (forceLoad) {
+            mForceLoadSet.put(t.key, new Boolean(true));
+        }
+        synchronized(this) {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Retrieves the next task from the load queue, as well as whether we want that task to be
+     * force reloaded.
+     */
+    Pair<Task, Boolean> nextTask() {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
+        }
+        Task task = mQueue.poll();
+        Boolean forceLoadTask = null;
+        if (task != null) {
+            forceLoadTask = mForceLoadSet.remove(task.key);
+        }
+        if (forceLoadTask == null) {
+            forceLoadTask = sFalse;
+        }
+        return new Pair<Task, Boolean>(task, forceLoadTask);
+    }
+
+    /** Removes a task from the load queue */
+    void removeTask(Task t) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
+        }
+        mQueue.remove(t);
+        mForceLoadSet.remove(t.key);
+    }
+
+    /** Clears all the tasks from the load queue */
+    void clearTasks() {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
+        }
+        mQueue.clear();
+        mForceLoadSet.clear();
+    }
+
+    /** Returns whether the load queue is empty */
+    boolean isEmpty() {
+        return mQueue.isEmpty();
+    }
+}
+
+/* Task resource loader */
+class TaskResourceLoader implements Runnable {
+    Context mContext;
+    HandlerThread mLoadThread;
+    Handler mLoadThreadHandler;
+    Handler mMainThreadHandler;
+
+    SystemServicesProxy mSystemServicesProxy;
+    TaskResourceLoadQueue mLoadQueue;
+    DrawableLruCache mApplicationIconCache;
+    BitmapLruCache mThumbnailCache;
+    Bitmap mDefaultThumbnail;
+
+    boolean mCancelled;
+    boolean mWaitingOnLoadQueue;
+
+    /** Constructor, creates a new loading thread that loads task resources in the background */
+    public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
+                              DrawableLruCache applicationIconCache,
+                              BitmapLruCache thumbnailCache,
+                              Bitmap defaultThumbnail) {
+        mLoadQueue = loadQueue;
+        mApplicationIconCache = applicationIconCache;
+        mThumbnailCache = thumbnailCache;
+        mDefaultThumbnail = defaultThumbnail;
+        mMainThreadHandler = new Handler();
+        mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
+        mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
+        mLoadThread.start();
+        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
+        mLoadThreadHandler.post(this);
+    }
+
+    /** Restarts the loader thread */
+    void start(Context context) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
+        }
+        mContext = context;
+        mCancelled = false;
+        mSystemServicesProxy = new SystemServicesProxy(context);
+        // Notify the load thread to start loading
+        synchronized(mLoadThread) {
+            mLoadThread.notifyAll();
+        }
+    }
+
+    /** Requests the loader thread to stop after the current iteration */
+    void stop() {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
+        }
+        // Mark as cancelled for the thread to pick up
+        mCancelled = true;
+        mSystemServicesProxy = null;
+        // If we are waiting for the load queue for more tasks, then we can just reset the
+        // Context now, since nothing is using it
+        if (mWaitingOnLoadQueue) {
+            mContext = null;
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            if (Console.Enabled) {
+                Console.log(Constants.Log.App.TaskDataLoader,
+                        "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
+            }
+            if (mCancelled) {
+                if (Console.Enabled) {
+                    Console.log(Constants.Log.App.TaskDataLoader,
+                            "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
+                }
+                // We have to unset the context here, since the background thread may be using it
+                // when we call stop()
+                mContext = null;
+                // If we are cancelled, then wait until we are started again
+                synchronized(mLoadThread) {
+                    try {
+                        if (Console.Enabled) {
+                            Console.log(Constants.Log.App.TaskDataLoader,
+                                    "[TaskResourceLoader|waitOnLoadThreadCancelled]");
+                        }
+                        mLoadThread.wait();
+                    } catch (InterruptedException ie) {
+                        ie.printStackTrace();
+                    }
+                }
+            } else {
+                SystemServicesProxy ssp = mSystemServicesProxy;
+
+                // Load the next item from the queue
+                Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
+                final Task t = nextTaskData.first;
+                final boolean forceLoadTask = nextTaskData.second;
+                if (t != null) {
+                    Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
+                    Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
+                    if (Console.Enabled) {
+                        Console.log(Constants.Log.App.TaskDataLoader,
+                                "  [TaskResourceLoader|load]",
+                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
+                                        " forceLoad: " + forceLoadTask);
+                    }
+                    // Load the application icon
+                    if (loadIcon == null || forceLoadTask) {
+                        ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
+                                t.userId);
+                        Drawable icon = ssp.getActivityIcon(info, t.userId);
+                        if (!mCancelled) {
+                            if (icon != null) {
+                                if (Console.Enabled) {
+                                    Console.log(Constants.Log.App.TaskDataLoader,
+                                            "    [TaskResourceLoader|loadIcon]", icon);
+                                }
+                                loadIcon = icon;
+                                mApplicationIconCache.put(t.key, icon);
+                            }
+                        }
+                    }
+                    // Load the thumbnail
+                    if (loadThumbnail == null || forceLoadTask) {
+                        Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
+                        if (!mCancelled) {
+                            if (thumbnail != null) {
+                                if (Console.Enabled) {
+                                    Console.log(Constants.Log.App.TaskDataLoader,
+                                            "    [TaskResourceLoader|loadThumbnail]", thumbnail);
+                                }
+                                thumbnail.setHasAlpha(false);
+                                loadThumbnail = thumbnail;
+                                mThumbnailCache.put(t.key, thumbnail);
+                            } else {
+                                loadThumbnail = mDefaultThumbnail;
+                                Console.logError(mContext,
+                                        "Failed to load task top thumbnail for: " +
+                                                t.key.baseIntent.getComponent().getPackageName());
+                            }
+                        }
+                    }
+                    if (!mCancelled) {
+                        // Notify that the task data has changed
+                        final Drawable newIcon = loadIcon;
+                        final Bitmap newThumbnail = loadThumbnail;
+                        mMainThreadHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                t.notifyTaskDataLoaded(newThumbnail, newIcon);
+                            }
+                        });
+                    }
+                }
+
+                // If there are no other items in the list, then just wait until something is added
+                if (!mCancelled && mLoadQueue.isEmpty()) {
+                    synchronized(mLoadQueue) {
+                        try {
+                            if (Console.Enabled) {
+                                Console.log(Constants.Log.App.TaskDataLoader,
+                                        "[TaskResourceLoader|waitOnLoadQueue]");
+                            }
+                            mWaitingOnLoadQueue = true;
+                            mLoadQueue.wait();
+                            mWaitingOnLoadQueue = false;
+                        } catch (InterruptedException ie) {
+                            ie.printStackTrace();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+/* Recents task loader
+ * NOTE: We should not hold any references to a Context from a static instance */
+public class RecentsTaskLoader {
+    static RecentsTaskLoader sInstance;
+
+    SystemServicesProxy mSystemServicesProxy;
+    DrawableLruCache mApplicationIconCache;
+    BitmapLruCache mThumbnailCache;
+    TaskResourceLoadQueue mLoadQueue;
+    TaskResourceLoader mLoader;
+
+    RecentsPackageMonitor mPackageMonitor;
+
+    int mMaxThumbnailCacheSize;
+    int mMaxIconCacheSize;
+
+    BitmapDrawable mDefaultApplicationIcon;
+    Bitmap mDefaultThumbnail;
+    Bitmap mLoadingThumbnail;
+
+    /** Private Constructor */
+    private RecentsTaskLoader(Context context) {
+        // Calculate the cache sizes, we just use a reasonable number here similar to those
+        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
+        // for icons.
+        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+        mMaxThumbnailCacheSize = maxMemory / 8;
+        mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
+        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+                mMaxIconCacheSize;
+        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+                mMaxThumbnailCacheSize;
+
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader,
+                    "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
+                    " iconCache: " + iconCacheSize);
+        }
+
+        // Initialize the proxy, cache and loaders
+        mSystemServicesProxy = new SystemServicesProxy(context);
+        mPackageMonitor = new RecentsPackageMonitor();
+        mLoadQueue = new TaskResourceLoadQueue();
+        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
+        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
+        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
+                mDefaultThumbnail);
+
+        // Create the default assets
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        icon.eraseColor(0x00000000);
+        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        mDefaultThumbnail.eraseColor(0xFFffffff);
+        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        mLoadingThumbnail.eraseColor(0x00000000);
+        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader,
+                    "[RecentsTaskLoader|defaultBitmaps]",
+                    "icon: " + mDefaultApplicationIcon +
+                    " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
+        }
+    }
+
+    /** Initializes the recents task loader */
+    public static RecentsTaskLoader initialize(Context context) {
+        if (sInstance == null) {
+            sInstance = new RecentsTaskLoader(context);
+        }
+        return sInstance;
+    }
+
+    /** Returns the current recents task loader */
+    public static RecentsTaskLoader getInstance() {
+        return sInstance;
+    }
+
+    /** Returns the system services proxy */
+    public SystemServicesProxy getSystemServicesProxy() {
+        return mSystemServicesProxy;
+    }
+
+    private List<ActivityManager.RecentTaskInfo> getRecentTasks() {
+        long t1 = System.currentTimeMillis();
+
+        SystemServicesProxy ssp = mSystemServicesProxy;
+        List<ActivityManager.RecentTaskInfo> tasks =
+                ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
+        Collections.reverse(tasks);
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TimeSystemCalls,
+                    "[RecentsTaskLoader|getRecentTasks]",
+                    "" + (System.currentTimeMillis() - t1) + "ms");
+            Console.log(Constants.Log.App.TaskDataLoader,
+                    "[RecentsTaskLoader|tasks]", "" + tasks.size());
+        }
+
+        return tasks;
+    }
+
+    /** Reload the set of recent tasks */
+    public SpaceNode reload(Context context, int preloadCount) {
+        long t1 = System.currentTimeMillis();
+
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
+        }
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        Resources res = context.getResources();
+        ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
+        TaskStack stack = new TaskStack(context);
+        SpaceNode root = new SpaceNode(context);
+        root.setStack(stack);
+
+        // Get the recent tasks
+        SystemServicesProxy ssp = mSystemServicesProxy;
+        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks();
+
+        // Add each task to the task stack
+        t1 = System.currentTimeMillis();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            ActivityManager.RecentTaskInfo t = tasks.get(i);
+            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
+            if (info == null) continue;
+
+            ActivityManager.TaskDescription av = t.taskDescription;
+            String activityLabel = null;
+            Drawable activityIcon = null;
+            int activityColor = config.taskBarViewDefaultBackgroundColor;
+            if (av != null) {
+                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
+                activityIcon = (av.getIcon() != null) ?
+                        ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
+                activityColor = av.getPrimaryColor();
+            } else {
+                activityLabel = ssp.getActivityLabel(info);
+            }
+            boolean isForemostTask = (i == (taskCount - 1));
+
+            // Create a new task
+            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
+                    activityIcon, activityColor, t.userId, t.lastActiveTime);
+
+            // Preload the specified number of apps
+            if (i >= (taskCount - preloadCount)) {
+                if (Console.Enabled) {
+                    Console.log(Constants.Log.App.TaskDataLoader,
+                            "[RecentsTaskLoader|preloadTask]",
+                            "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
+                }
+
+                // Load the icon from the cache if possible
+                task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
+                if (task.applicationIcon == null) {
+                    if (isForemostTask) {
+                        // We force loading the application icon for the foremost task
+                        task.applicationIcon = ssp.getActivityIcon(info, task.userId);
+                        if (task.applicationIcon != null) {
+                            mApplicationIconCache.put(task.key, task.applicationIcon);
+                        } else {
+                            task.applicationIcon = mDefaultApplicationIcon;
+                        }
+                    } else {
+                        // Either the task has updated, or we haven't cached any information for the
+                        // task, so reload it
+                        tasksToForceLoad.add(task);
+                    }
+                }
+
+                // Load the thumbnail (if possible and not the foremost task, from the cache)
+                task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
+                if (task.thumbnail == null) {
+                    if (Console.Enabled) {
+                        Console.log(Constants.Log.App.TaskDataLoader,
+                                "[RecentsTaskLoader|loadingTaskThumbnail]");
+                    }
+                    if (isForemostTask) {
+                        // We force loading the thumbnail icon for the foremost task
+                        task.thumbnail = ssp.getTaskThumbnail(task.key.id);
+                        if (task.thumbnail != null) {
+                            task.thumbnail.setHasAlpha(false);
+                            mThumbnailCache.put(task.key, task.thumbnail);
+                        } else {
+                            task.thumbnail = mDefaultThumbnail;
+                        }
+                    } else {
+                        // Either the task has updated, or we haven't cached any information for the
+                        // task, so reload it
+                        tasksToForceLoad.add(task);
+                    }
+                }
+            }
+
+            // Add the task to the stack
+            if (Console.Enabled) {
+                Console.log(Constants.Log.App.TaskDataLoader,
+                        "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
+            }
+            stack.addTask(task);
+        }
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TimeSystemCalls,
+                    "[RecentsTaskLoader|getAllTaskTopThumbnail]",
+                    "" + (System.currentTimeMillis() - t1) + "ms");
+        }
+
+        // Start the task loader
+        mLoader.start(context);
+
+        // Add all the tasks that we are force/re-loading
+        for (Task t : tasksToForceLoad) {
+            mLoadQueue.addTask(t, true);
+        }
+
+        // Update the package monitor with the list of packages to listen for
+        mPackageMonitor.setTasks(tasks);
+
+        return root;
+    }
+
+    /** Acquires the task resource data directly from the pool. */
+    public void loadTaskData(Task t) {
+        Drawable applicationIcon = mApplicationIconCache.get(t.key);
+        Bitmap thumbnail = mThumbnailCache.get(t.key);
+
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
+                    t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
+                            " thumbnailCacheSize: " + mThumbnailCache.size());
+        }
+
+        boolean requiresLoad = false;
+        if (applicationIcon == null) {
+            applicationIcon = mDefaultApplicationIcon;
+            requiresLoad = true;
+        }
+        if (thumbnail == null) {
+            thumbnail = mLoadingThumbnail;
+            requiresLoad = true;
+        }
+        if (requiresLoad) {
+            mLoadQueue.addTask(t, false);
+        }
+        t.notifyTaskDataLoaded(thumbnail, applicationIcon);
+    }
+
+    /** Releases the task resource data back into the pool. */
+    public void unloadTaskData(Task t) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader,
+                    "[RecentsTaskLoader|unloadTask]", t +
+                    " thumbnailCacheSize: " + mThumbnailCache.size());
+        }
+
+        mLoadQueue.removeTask(t);
+        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
+    }
+
+    /** Completely removes the resource data from the pool. */
+    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader,
+                    "[RecentsTaskLoader|deleteTask]", t);
+        }
+
+        mLoadQueue.removeTask(t);
+        mThumbnailCache.remove(t.key);
+        mApplicationIconCache.remove(t.key);
+        if (notifyTaskDataUnloaded) {
+            t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
+        }
+    }
+
+    /** Stops the task loader and clears all pending tasks */
+    void stopLoader() {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
+        }
+        mLoader.stop();
+        mLoadQueue.clearTasks();
+    }
+
+    /** Registers any broadcast receivers. */
+    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
+        // Register the broadcast receiver to handle messages related to packages being added/removed
+        mPackageMonitor.register(context, cb);
+    }
+
+    /** Unregisters any broadcast receivers. */
+    public void unregisterReceivers() {
+        mPackageMonitor.unregister();
+    }
+
+    /**
+     * Handles signals from the system, trimming memory when requested to prevent us from running
+     * out of memory.
+     */
+    public void onTrimMemory(int level) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
+                    Console.trimMemoryLevelToString(level));
+        }
+
+        switch (level) {
+            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+                // Stop the loader immediately when the UI is no longer visible
+                stopLoader();
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+                // We are leaving recents, so trim the data a bit
+                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
+                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+                // We are going to be low on memory
+                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
+                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+                // We are low on memory, so release everything
+                mThumbnailCache.evictAll();
+                mApplicationIconCache.evictAll();
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
index 1dd1be6..20be415 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
@@ -56,6 +56,13 @@
         return mStack;
     }
 
+    /** Returns whether there are any tasks in any stacks below this node. */
+    public boolean hasTasks() {
+        return (mStack.getTaskCount() > 0) ||
+                (mStartNode != null && mStartNode.hasTasks()) ||
+                (mEndNode != null && mEndNode.hasTasks());
+    }
+
     /** Returns whether this is a leaf node */
     boolean isLeafNode() {
         return (mStartNode == null) && (mEndNode == null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 47a506c..abfb221 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -18,8 +18,8 @@
 
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import com.android.systemui.recents.Utilities;
 
 
 /**
@@ -29,7 +29,7 @@
     /* Task callbacks */
     public interface TaskCallbacks {
         /* Notifies when a task has been bound */
-        public void onTaskDataLoaded(boolean reloadingTaskData);
+        public void onTaskDataLoaded();
         /* Notifies when a task has been unbound */
         public void onTaskDataUnloaded();
     }
@@ -39,11 +39,17 @@
         public final int id;
         public final Intent baseIntent;
         public final int userId;
+        public long lastActiveTime;
 
-        public TaskKey(int id, Intent intent, int userId) {
+        public TaskKey(int id, Intent intent, int userId, long lastActiveTime) {
             this.id = id;
             this.baseIntent = intent;
             this.userId = userId;
+            this.lastActiveTime = lastActiveTime;
+        }
+
+        public void updateLastActiveTime(long lastActiveTime) {
+            this.lastActiveTime = lastActiveTime;
         }
 
         @Override
@@ -63,7 +69,8 @@
         @Override
         public String toString() {
             return "Task.Key: " + id + ", "
-                    + "u" + userId + ", "
+                    + "u: " + userId + ", "
+                    + "lat: " + lastActiveTime + ", "
                     + baseIntent.getComponent().getPackageName();
         }
     }
@@ -73,6 +80,7 @@
     public Drawable activityIcon;
     public String activityLabel;
     public int colorPrimary;
+    public int colorPrimaryGreyscale;
     public Bitmap thumbnail;
     public boolean isActive;
     public int userId;
@@ -84,11 +92,12 @@
     }
 
     public Task(int id, boolean isActive, Intent intent, String activityTitle,
-                BitmapDrawable activityIcon, int colorPrimary, int userId) {
-        this.key = new TaskKey(id, intent, userId);
+                Drawable activityIcon, int colorPrimary, int userId, long lastActiveTime) {
+        this.key = new TaskKey(id, intent, userId, lastActiveTime);
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
         this.colorPrimary = colorPrimary;
+        this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
         this.isActive = isActive;
         this.userId = userId;
     }
@@ -99,12 +108,11 @@
     }
 
     /** Notifies the callback listeners that this task has been loaded */
-    public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon,
-                                     boolean reloadingTaskData) {
+    public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
         this.applicationIcon = applicationIcon;
         this.thumbnail = thumbnail;
         if (mCb != null) {
-            mCb.onTaskDataLoaded(reloadingTaskData);
+            mCb.onTaskDataLoaded();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
new file mode 100644
index 0000000..6568b1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
@@ -0,0 +1,260 @@
+/*
+ * 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.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.systemui.R;
+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 FullscreenTransitionOverlayView extends FrameLayout {
+
+    /** The FullscreenTransitionOverlayView callbacks */
+    public interface FullScreenTransitionViewCallbacks {
+        void onEnterAnimationComplete(boolean canceled);
+    }
+
+    RecentsConfiguration mConfig;
+
+    FullScreenTransitionViewCallbacks mCb;
+
+    ImageView mScreenshotView;
+    Rect mClipRect = new Rect();
+    Paint mLayerPaint = new Paint();
+
+    boolean mIsAnimating;
+    AnimatorSet mEnterAnimation;
+
+    public FullscreenTransitionOverlayView(Context context) {
+        super(context);
+    }
+
+    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr,
+                                           int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
+        setClipTop(getClipTop());
+        setClipBottom(getClipBottom());
+        setWillNotDraw(false);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mScreenshotView = (ImageView) findViewById(R.id.image);
+    }
+
+    /** Sets the callbacks */
+    public void setCallbacks(FullScreenTransitionViewCallbacks cb) {
+        mCb = cb;
+    }
+
+    /** 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.CLIP_TO_LAYER_SAVE_FLAG);
+        canvas.clipRect(mClipRect);
+        super.draw(canvas);
+        canvas.restoreToCount(restoreCount);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    /** 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
+        Rect taskRect = ctx.taskRect;
+        float scale = (float) taskRect.width() / getMeasuredWidth();
+        float scaleYOffset = ((1f - scale) * getMeasuredHeight()) / 2;
+        float scaledTopInset = (int) (scale * mConfig.systemInsets.top);
+        int translationY = (int) -scaleYOffset + (int) (mConfig.systemInsets.top - scaledTopInset) + taskRect.top;
+        int clipBottom = mConfig.systemInsets.top + (int) (taskRect.height() / scale);
+
+        // Enable the HW Layers on the screenshot view
+        mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+
+        // 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, mLayerPaint);
+
+                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, mLayerPaint);
+                }
+            });
+            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..e7eac67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -24,10 +24,12 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowInsets;
@@ -35,8 +37,8 @@
 import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsPackageMonitor;
-import com.android.systemui.recents.RecentsTaskLoader;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.SpaceNode;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -44,7 +46,6 @@
 import java.util.ArrayList;
 import java.util.Set;
 
-
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
  * to their SpaceNode bounds.
@@ -54,10 +55,15 @@
 
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
-        public void onTaskLaunching(boolean isTaskInStackBounds);
-        public void onEnterAnimationTriggered();
+        public void onTaskLaunching();
+        public void onLastTaskRemoved();
+        public void onExitToHomeAnimationTriggered();
     }
 
+    RecentsConfiguration mConfig;
+    LayoutInflater mInflater;
+    Paint mDebugModePaint;
+
     // The space partitioning root of this container
     SpaceNode mBSP;
     // Whether there are any tasks
@@ -67,12 +73,22 @@
     // Recents view callbacks
     RecentsViewCallbacks mCb;
 
-    LayoutInflater mInflater;
-
     public RecentsView(Context context) {
         super(context);
+    }
+
+    public RecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
-        setWillNotDraw(false);
     }
 
     /** Sets the callbacks */
@@ -94,6 +110,15 @@
             addView(stackView);
             mHasTasks |= (stack.getTaskCount() > 0);
         }
+
+        // Enable debug mode drawing
+        if (mConfig.debugModeEnabled) {
+            mDebugModePaint = new Paint();
+            mDebugModePaint.setColor(0xFFff0000);
+            mDebugModePaint.setStyle(Paint.Style.STROKE);
+            mDebugModePaint.setStrokeWidth(5f);
+            setWillNotDraw(false);
+        }
     }
 
     /** Launches the focused task from the first stack if possible */
@@ -160,18 +185,38 @@
     }
 
     /** 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 startEnterRecentsAnimation(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.startEnterRecentsAnimation(ctx);
+            }
+        }
+    }
+
+    /** Requests all task stacks to start their exit-recents animation */
+    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.increment();
 
         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.startExitToHomeAnimation(ctx);
             }
         }
+
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.decrement();
+
+        // Notify of the exit animation
+        mCb.onExitToHomeAnimationTriggered();
     }
 
     /** Adds the search bar */
@@ -215,10 +260,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 +273,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 +303,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
@@ -289,6 +332,43 @@
         }
     }
 
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        // Debug mode drawing
+        if (mConfig.debugModeEnabled) {
+            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint);
+        }
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.MeasureAndLayout,
+                    "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
+        }
+
+        // Update the configuration with the latest system insets and trigger a relayout
+        mConfig.updateSystemInsets(insets.getSystemWindowInsets());
+        requestLayout();
+
+        return insets.consumeSystemWindowInsets(false, false, false, true);
+    }
+
+    /** Notifies each task view of the user interaction. */
+    public void onUserInteraction() {
+        // Get the first stack view
+        TaskStackView stackView = null;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child instanceof TaskStackView) {
+                stackView = (TaskStackView) child;
+                stackView.onUserInteraction();
+            }
+        }
+    }
+
     /** Focuses the next task in the first stack view */
     public void focusNextTask(boolean forward) {
         // Get the first stack view
@@ -307,30 +387,6 @@
         }
     }
 
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.Draw, "[RecentsView|dispatchDraw]", "",
-                    Console.AnsiPurple);
-        }
-        super.dispatchDraw(canvas);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
-        }
-
-        // Update the configuration with the latest system insets and trigger a relayout
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        config.updateSystemInsets(insets.getSystemWindowInsets());
-        requestLayout();
-
-        return insets.consumeSystemWindowInsets(false, false, false, true);
-    }
-
     /** Unfilters any filtered stacks */
     public boolean unfilterFilteredStacks() {
         if (mBSP != null) {
@@ -355,56 +411,60 @@
                                final TaskStack stack, final Task task) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
-            boolean isTaskInStackBounds = false;
-            if (stackView != null && tv != null) {
-                isTaskInStackBounds = stackView.isTaskInStackBounds(tv);
-            }
-            mCb.onTaskLaunching(isTaskInStackBounds);
+            mCb.onTaskLaunching();
         }
 
+        // Upfront the processing of the thumbnail
+        TaskViewTransform transform;
+        View sourceView = tv;
+        int offsetX = 0;
+        int offsetY = 0;
+        int stackScroll = stackView.getStackScroll();
+        if (tv == null) {
+            // 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)
+            sourceView = stackView;
+            transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
+                    stackScroll);
+            offsetX = transform.rect.left;
+            offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
+        } else {
+            transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
+                    stackScroll);
+        }
+
+        // Compute the thumbnail to scale up from
+        ActivityOptions opts = null;
+        int thumbnailWidth = transform.rect.width();
+        int thumbnailHeight = transform.rect.height();
+        if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
+                task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
+            // Resize the thumbnail to the size of the view that we are animating from
+            Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
+                    Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(b);
+            c.drawBitmap(task.thumbnail,
+                    new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
+                    new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
+            c.setBitmap(null);
+            opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
+                    b, offsetX, offsetY);
+        }
+
+        final ActivityOptions launchOpts = opts;
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
-                TaskViewTransform transform;
-                View sourceView = tv;
-                int offsetX = 0;
-                int offsetY = 0;
-                int stackScroll = stackView.getStackScroll();
-                if (tv == null) {
-                    // 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());
-                } else {
-                    transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
-                }
-
-                // Compute the thumbnail to scale up from
-                ActivityOptions opts = null;
-                int thumbnailWidth = transform.rect.width();
-                int thumbnailHeight = transform.rect.height();
-                if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
-                        task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
-                    // Resize the thumbnail to the size of the view that we are animating from
-                    Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
-                            Bitmap.Config.ARGB_8888);
-                    Canvas c = new Canvas(b);
-                    c.drawBitmap(task.thumbnail,
-                            new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
-                            new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
-                    c.setBitmap(null);
-                    opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
-                            b, offsetX, offsetY);
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity");
                 }
 
                 if (task.isActive) {
                     // Bring an active task to the foreground
                     RecentsTaskLoader.getInstance().getSystemServicesProxy()
-                            .moveTaskToFront(task.key.id, opts);
+                            .moveTaskToFront(task.key.id, launchOpts);
                 } else {
                     // Launch the activity anew with the desired animation
                     Intent i = new Intent(task.key.baseIntent);
@@ -413,8 +473,8 @@
                             | Intent.FLAG_ACTIVITY_NEW_TASK);
                     try {
                         UserHandle taskUser = new UserHandle(task.userId);
-                        if (opts != null) {
-                            getContext().startActivityAsUser(i, opts.toBundle(), taskUser);
+                        if (launchOpts != null) {
+                            getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
                         } else {
                             getContext().startActivityAsUser(i, taskUser);
                         }
@@ -426,19 +486,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);
+            stackView.animateOnLaunchingTask(tv, launchRunnable);
         }
     }
 
@@ -469,6 +533,39 @@
                 isDocument);
     }
 
+    @Override
+    public void onLastTaskRemoved() {
+        mCb.onLastTaskRemoved();
+    }
+
+    @Override
+    public void onTaskStackFilterTriggered() {
+        // Hide the search bar
+        if (mSearchBar != null) {
+            mSearchBar.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .setDuration(mConfig.filteringCurrentViewsAnimDuration)
+                    .withLayer()
+                    .start();
+        }
+    }
+
+    @Override
+    public void onTaskStackUnfilterTriggered() {
+        // Show the search bar
+        if (mSearchBar != null) {
+            mSearchBar.animate()
+                    .alpha(1f)
+                    .setStartDelay(0)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .setDuration(mConfig.filteringNewViewsAnimDuration)
+                    .withLayer()
+                    .start();
+        }
+    }
+
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
new file mode 100644
index 0000000..5b17b41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -0,0 +1,109 @@
+/*
+ * 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.app.Activity;
+import android.view.View;
+import com.android.systemui.R;
+import com.android.systemui.recents.RecentsConfiguration;
+
+/** Manages the scrims for the various system bars. */
+public class SystemBarScrimViews {
+
+    RecentsConfiguration mConfig;
+
+    View mStatusBarScrimView;
+    View mNavBarScrimView;
+
+    boolean mHasNavBarScrim;
+    boolean mShouldAnimateStatusBarScrim;
+    boolean mHasStatusBarScrim;
+    boolean mShouldAnimateNavBarScrim;
+
+    public SystemBarScrimViews(Activity activity, RecentsConfiguration config) {
+        mConfig = config;
+        mStatusBarScrimView = activity.findViewById(R.id.status_bar_scrim);
+        mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
+    }
+
+    /**
+     * Prepares the scrim views for animating when entering Recents. This will be called before
+     * the first draw.
+     */
+    public void prepareEnterRecentsAnimation() {
+        mHasNavBarScrim = mConfig.hasNavBarScrim();
+        mShouldAnimateNavBarScrim = mConfig.shouldAnimateNavBarScrim();
+        mHasStatusBarScrim = mConfig.hasStatusBarScrim();
+        mShouldAnimateStatusBarScrim = mConfig.shouldAnimateStatusBarScrim();
+
+        mNavBarScrimView.setVisibility(mHasNavBarScrim && !mShouldAnimateNavBarScrim ?
+                View.VISIBLE : View.INVISIBLE);
+        mStatusBarScrimView.setVisibility(mHasStatusBarScrim && !mShouldAnimateStatusBarScrim ?
+                View.VISIBLE : View.INVISIBLE);
+    }
+
+    /**
+     * Starts animating the scrim views when entering Recents.
+     */
+    public void startEnterRecentsAnimation() {
+        if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
+            mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
+            mStatusBarScrimView.animate()
+                    .translationY(0)
+                    .setStartDelay(mConfig.taskBarEnterAnimDelay)
+                    .setDuration(mConfig.navBarScrimEnterDuration)
+                    .setInterpolator(mConfig.quintOutInterpolator)
+                    .withStartAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mStatusBarScrimView.setVisibility(View.VISIBLE);
+                        }
+                    })
+                    .start();
+        }
+        if (mHasNavBarScrim && mShouldAnimateNavBarScrim) {
+            mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
+            mNavBarScrimView.animate()
+                    .translationY(0)
+                    .setStartDelay(mConfig.taskBarEnterAnimDelay)
+                    .setDuration(mConfig.navBarScrimEnterDuration)
+                    .setInterpolator(mConfig.quintOutInterpolator)
+                    .withStartAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mNavBarScrimView.setVisibility(View.VISIBLE);
+                        }
+                    })
+                    .start();
+        }
+    }
+
+    /**
+     * Starts animating the scrim views when leaving Recents (either via launching a task, or
+     * going home).
+     */
+    public void startExitRecentsAnimation() {
+        if (mHasNavBarScrim && mShouldAnimateNavBarScrim) {
+            mNavBarScrimView.animate()
+                    .translationY(mNavBarScrimView.getMeasuredHeight())
+                    .setStartDelay(0)
+                    .setDuration(mConfig.taskBarExitAnimDuration)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .start();
+        }
+    }
+}
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..2c0dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -23,7 +23,11 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -36,6 +40,9 @@
 
 /* The task bar view */
 class TaskBarView extends FrameLayout {
+
+    RecentsConfiguration mConfig;
+
     Task mTask;
 
     ImageView mDismissButton;
@@ -45,6 +52,7 @@
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
 
+    Paint mLayerPaint = new Paint();
     static Paint sHighlightPaint;
 
     public TaskBarView(Context context) {
@@ -61,6 +69,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,59 +79,77 @@
 
         // 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);
         }
     }
 
     @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // We ignore taps on the task bar except on the filter and dismiss buttons
+        if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
+        if (mConfig.debugModeEnabled) return true;
+
+        return super.onTouchEvent(event);
+    }
+
+    @Override
     protected void onFinishInflate() {
         // Initialize the icon and description views
         mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
         mActivityDescription = (TextView) findViewById(R.id.activity_description);
         mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
+
+        // Hide the backgrounds if they are ripple drawables
+        if (!Constants.DebugFlags.App.EnableTaskFiltering) {
+            if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
+                mApplicationIcon.setBackground(null);
+            }
+        }
     }
 
     @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);
     }
 
     /** 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);
-            }
-            mDismissButton.animate()
-                    .alpha(toTransform.dismissAlpha)
+    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
+        if (duration > 0 && (mDismissButton.getVisibility() == View.VISIBLE)) {
+            ViewPropertyAnimator anim = mDismissButton.animate();
+
+            // Animate to the final state
+            if (toTransform.hasDismissAlphaChangedFrom(mDismissButton.getAlpha())) {
+                anim.alpha(toTransform.dismissAlpha)
                     .setStartDelay(0)
                     .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
+            }
         } else {
-            mDismissButton.setAlpha(toTransform.dismissAlpha);
+            // Set the changed properties
+            if (toTransform.hasDismissAlphaChangedFrom(mDismissButton.getAlpha())) {
+                mDismissButton.setAlpha(toTransform.dismissAlpha);
+            }
         }
-        mDismissButton.invalidate();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
     }
 
     /** Binds the bar view to the task */
-    void rebindToTask(Task t, boolean animate) {
-        RecentsConfiguration configuration = RecentsConfiguration.getInstance();
+    void rebindToTask(Task t) {
         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
@@ -133,20 +160,12 @@
         }
         mActivityDescription.setText(t.activityLabel);
         // Try and apply the system ui tint
-        int tint = t.colorPrimary;
-        if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) {
-            setBackgroundColor(tint);
-            mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint,
-                    configuration.taskBarViewLightTextColor, configuration.taskBarViewDarkTextColor));
-            mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint,
-                    mLightDismissDrawable, mDarkDismissDrawable));
-        } else {
-            setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor);
-            mActivityDescription.setTextColor(configuration.taskBarViewDefaultTextColor);
-        }
-        if (animate) {
-            // XXX: Investigate how expensive it will be to create a second bitmap and crossfade
-        }
+        setBackgroundColor(t.colorPrimary);
+        mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColorGreyscale(
+                t.colorPrimaryGreyscale, mConfig.taskBarViewLightTextColor,
+                mConfig.taskBarViewDarkTextColor));
+        mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColorGreyscale(
+                t.colorPrimaryGreyscale, mLightDismissDrawable, mDarkDismissDrawable));
     }
 
     /** Unbinds the bar view from the task */
@@ -155,4 +174,88 @@
         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 prepareEnterRecentsAnimation() {
+        setVisibility(View.INVISIBLE);
+    }
+
+    /** Animates this task bar as it enters recents */
+    public void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
+        // Animate the task bar of the first task view
+        setVisibility(View.VISIBLE);
+        setTranslationY(-getMeasuredHeight());
+        animate()
+                .translationY(0)
+                .setStartDelay(delay)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskBarEnterAnimDuration)
+                .withEndAction(postAnimRunnable)
+                .start();
+    }
+
+    /** Animates this task bar as it exits recents */
+    public void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable) {
+        // Animate the task bar out of the first task view
+        animate()
+                .translationY(-getMeasuredHeight())
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                .setDuration(mConfig.taskBarExitAnimDuration)
+                .withStartAction(preAnimRunnable)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        post(postAnimRunnable);
+                    }
+                })
+                .start();
+    }
+
+    /** Animates this task bar dismiss button when launching a task. */
+    public void startLaunchTaskDismissAnimation() {
+        if (mDismissButton.getVisibility() == View.VISIBLE) {
+            mDismissButton.animate().cancel();
+            mDismissButton.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .setDuration(mConfig.taskBarExitAnimDuration)
+                    .withLayer()
+                    .start();
+        }
+    }
+
+    /** Animates this task bar if the user does not interact with the stack after a certain time. */
+    public void startNoUserInteractionAnimation() {
+        mDismissButton.setVisibility(View.VISIBLE);
+        mDismissButton.setAlpha(0f);
+        mDismissButton.animate()
+                .alpha(1f)
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                .setDuration(mConfig.taskBarEnterAnimDuration)
+                .withLayer()
+                .start();
+    }
+
+    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
+    public void setNoUserInteractionState() {
+        if (mDismissButton.getVisibility() != View.VISIBLE) {
+            mDismissButton.animate().cancel();
+            mDismissButton.setVisibility(View.VISIBLE);
+            mDismissButton.setAlpha(1f);
+        }
+    }
+
+    /** Enable the hw layers on this task view */
+    void enableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+    }
+
+    /** Disable the hw layers on this task view */
+    void disableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
+    }
 }
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..29eaa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,33 +20,29 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.OverScroller;
 import com.android.systemui.R;
 import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.DozeTrigger;
 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.RecentsPackageMonitor;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Set;
 
 
@@ -60,18 +56,21 @@
         public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
         public void onTaskAppInfoLaunched(Task t);
         public void onTaskRemoved(Task t);
+        public void onLastTaskRemoved();
+        public void onTaskStackFilterTriggered();
+        public void onTaskStackUnfilterTriggered();
     }
 
+    RecentsConfiguration mConfig;
+
     TaskStack mStack;
+    TaskStackViewLayoutAlgorithm mStackAlgorithm;
+    TaskStackViewFilterAlgorithm mFilterAlgorithm;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
     ViewPool<TaskView, Task> mViewPool;
-
-    // The various rects that define the stack view
-    Rect mRect = new Rect();
-    Rect mStackRect = new Rect();
-    Rect mStackRectSansPeek = new Rect();
-    Rect mTaskRect = new Rect();
+    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
+    DozeTrigger mUIDozeTrigger;
 
     // The virtual stack scroll that we use for the card layout
     int mStackScroll;
@@ -83,24 +82,76 @@
     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();
     LayoutInflater mInflater;
 
+    Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
+        @Override
+        public void run() {
+            int childCount = getChildCount();
+            for (int i = childCount - 1; i >= 0; i--) {
+                mViewPool.returnViewToPool((TaskView) getChildAt(i));
+            }
+        }
+    };
+
     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);
+        mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
+        mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
+        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
+            @Override
+            public void run() {
+                // Show the task bar dismiss buttons
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    tv.startNoUserInteractionAnimation();
+                }
+            }
+        });
+        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 +169,7 @@
                     "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
         }
         if (!mStackViewsDirty) {
-            invalidate();
+            invalidate(mStackAlgorithm.mStackRect);
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -129,8 +180,8 @@
         mStackViewsDirty = true;
     }
 
-    // XXX: Optimization: Use a mapping of Task -> View
-    private TaskView getChildViewForTask(Task t) {
+    /** Finds the child view given a specific task */
+    TaskView getChildViewForTask(Task t) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView tv = (TaskView) getChildAt(i);
@@ -141,76 +192,41 @@
         return null;
     }
 
-    /** Update/get the transform */
-    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
-        TaskViewTransform transform = new TaskViewTransform();
-
-        // Return early if we have an invalid index
-        if (indexInStack < 0) return transform;
-
-        // Map the items to an continuous position relative to the specified scroll
-        int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
-        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
-        float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
-        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
-        float boundedT = Math.max(t, -(numPeekCards + 1));
-
-        // Set the scale relative to its position
-        int numFrontScaledCards = 3;
-        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
-        float scaleRange = 1f - minScale;
-        float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
-        float scale = Math.max(minScale, Math.min(1f, minScale + 
-            ((boundedT + (numPeekCards + 1)) * scaleInc)));
-        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
-        transform.scale = scale;
-
-        // Set the translation
-        if (boundedT < 0f) {
-            transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
-                    numPeekCards) * peekHeight - scaleYOffset);
-        } else {
-            transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
-        }
-
-        // Set the z translation
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        int minZ = config.taskViewTranslationZMinPx;
-        int incZ = config.taskViewTranslationZIncrementPx;
-        transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
-
-        // Set the alphas
-        transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
-
-        // Update the rect and visibility
-        transform.rect.set(mTaskRect);
-        if (t < -(numPeekCards + 1)) {
-            transform.visible = false;
-        } else {
-            transform.rect.offset(0, transform.translationY);
-            Utilities.scaleRectAboutCenter(transform.rect, transform.scale);
-            transform.visible = Rect.intersects(mRect, transform.rect);
-        }
-        transform.t = t;
-        return transform;
+    /** Returns the stack algorithm for this task stack. */
+    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
+        return mStackAlgorithm;
     }
 
     /**
      * 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 taskTransformCount = taskTransforms.size();
         int taskCount = tasks.size();
         int firstVisibleIndex = -1;
         int lastVisibleIndex = -1;
+
+        // We can reuse the task transforms where possible to reduce object allocation
+        if (taskTransformCount < taskCount) {
+            // If there are less transforms than tasks, then add as many transforms as necessary
+            for (int i = taskTransformCount; i < taskCount; i++) {
+                taskTransforms.add(new TaskViewTransform());
+            }
+        } else if (taskTransformCount > taskCount) {
+            // If there are more transforms than tasks, then just subset the transform list
+            taskTransforms.subList(0, taskCount);
+        }
+
+        // Update the stack transforms
         for (int i = 0; i < taskCount; i++) {
-            TaskViewTransform transform = getStackTransform(i, stackScroll);
-            taskTransforms.add(transform);
+            TaskViewTransform transform = mStackAlgorithm.getStackTransform(i, stackScroll,
+                    taskTransforms.get(i));
             if (transform.visible) {
                 if (firstVisibleIndex < 0) {
                     firstVisibleIndex = i;
@@ -219,13 +235,27 @@
             }
 
             if (boundTranslationsToRect) {
-                transform.translationY = Math.min(transform.translationY, mRect.bottom);
+                transform.translationY = Math.min(transform.translationY,
+                        mStackAlgorithm.mRect.bottom);
             }
         }
         if (visibleRangeOut != null) {
             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 +275,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) {
@@ -263,8 +292,8 @@
                         if (mStackViewsAnimationDuration > 0 && i != 0) {
                             int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
                                     (visibleRange[1] + 1);
-                            tv.updateViewPropertiesToTaskTransform(null,
-                                    getStackTransform(fromIndex, stackScroll), 0);
+                            tv.updateViewPropertiesToTaskTransform(
+                                    mStackAlgorithm.getStackTransform(fromIndex, stackScroll), 0);
                         }
                     }
                 } else {
@@ -281,10 +310,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(mTaskTransforms.get(taskIndex),
                             mStackViewsAnimationDuration);
                 }
             }
@@ -302,29 +331,24 @@
     /** Sets the current stack scroll */
     public void setStackScroll(int value) {
         mStackScroll = value;
+        mUIDozeTrigger.poke();
         requestSynchronizeStackViewsWithModel();
     }
     /** Sets the current stack scroll without synchronizing the stack view with the model */
     public void setStackScrollRaw(int value) {
         mStackScroll = value;
+        mUIDozeTrigger.poke();
     }
     /** Sets the current stack scroll to the initial state when you first enter recents */
     public void setStackScrollToInitialState() {
-        if (mStack.getTaskCount() > 2) {
-            int initialScroll = mMaxScroll - mTaskRect.height() / 2;
-            setStackScroll(initialScroll);
-        } else {
-            setStackScroll(mMaxScroll);
-        }
+        setStackScroll(getInitialStackScroll());
     }
-
-    /**
-     * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
-     * is not bounded)
-     */
-    int getStackScrollForTaskIndex(int i) {
-        int taskHeight = mTaskRect.height();
-        return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
+    /** Computes the initial stack scroll for the stack. */
+    int getInitialStackScroll() {
+        if (mStack.getTaskCount() > 2) {
+            return mMaxScroll - mStackAlgorithm.mTaskRect.height() / 2;
+        }
+        return mMaxScroll;
     }
 
     /** Gets the current stack scroll */
@@ -361,7 +385,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) {
@@ -438,29 +462,12 @@
         return getScrollAmountOutOfBounds(getStackScroll()) != 0;
     }
 
-    /** Returns whether the task view is in the stack bounds or not */
-    boolean isTaskInStackBounds(TaskView tv) {
-        Rect r = new Rect();
-        tv.getHitRect(r);
-        return r.bottom <= mRect.bottom;
-    }
-
     /** Updates the min and max virtual scroll bounds */
     void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
         // Compute the min and max scroll values
-        int numTasks = Math.max(1, mStack.getTaskCount());
-        int taskHeight = mTaskRect.height();
-        int stackHeight = mStackRectSansPeek.height();
-        int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
-                Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
-
-        if (numTasks <= 1) {
-            // If there is only one task, then center the task in the stack rect (sans peek)
-            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
-        } else {
-            mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
-            mMaxScroll = maxScrollHeight - stackHeight;
-        }
+        mStackAlgorithm.computeMinMaxScroll(mStack.getTaskCount());
+        mMinScroll = mStackAlgorithm.mMinScroll;
+        mMaxScroll = mStackAlgorithm.mMaxScroll;
 
         // Debug logging
         if (Constants.Log.UI.MeasureAndLayout) {
@@ -473,6 +480,20 @@
         }
     }
 
+    /** Animates a task view in this stack as it launches. */
+    public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
+        // Hide each of the task bar dismiss buttons
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView t = (TaskView) getChildAt(i);
+            if (t == tv) {
+                t.startLaunchTaskAnimation(r, true);
+            } else {
+                t.startLaunchTaskAnimation(null, false);
+            }
+        }
+    }
+
     /** Focuses the task at the specified index in the stack */
     void focusTask(int taskIndex, boolean scrollToNewPosition) {
         if (Console.Enabled) {
@@ -510,7 +531,7 @@
             if (scrollToNewPosition) {
                 // Scroll the view into position
                 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
-                        getStackScrollForTaskIndex(taskIndex)));
+                        mStackAlgorithm.getStackScrollForTaskIndex(taskIndex)));
 
                 animateScroll(getStackScroll(), newScroll, postScrollRunnable);
             } else {
@@ -543,54 +564,24 @@
     /** 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");
-        }
-    }
-
-    @Override
-    public void computeScroll() {
-        if (mScroller.computeScrollOffset()) {
-            setStackScroll(mScroller.getCurrY());
-            invalidate();
-
-            // If we just finished scrolling, then disable the hw layers
-            if (mScroller.isFinished()) {
-                decHwLayersRefCount("finishedFlingScroll");
-            }
-        }
+        mHwLayersTrigger.decrement();
     }
 
     @Override
@@ -604,6 +595,19 @@
     }
 
     @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            setStackScroll(mScroller.getCurrY());
+            invalidate(mStackAlgorithm.mStackRect);
+
+            // If we just finished scrolling, then disable the hw layers
+            if (mScroller.isFinished()) {
+                decHwLayersRefCount("finishedFlingScroll");
+            }
+        }
+    }
+
+    @Override
     public void dispatchDraw(Canvas canvas) {
         if (Console.Enabled) {
             Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
@@ -616,7 +620,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 +635,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);
@@ -657,35 +660,8 @@
 
     /** Computes the stack and task rects */
     public void computeRects(int width, int height, int insetLeft, int insetBottom) {
-        // 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);
-        mStackRect.set(mRect);
-        mStackRect.left += insetLeft;
-        mStackRect.bottom -= insetBottom;
-
-        int widthPadding = (int) (config.taskStackWidthPaddingPct * mStackRect.width());
-        int heightPadding = config.taskStackTopPaddingPx;
-        if (Constants.DebugFlags.App.EnableSearchLayout) {
-            mStackRect.top += heightPadding;
-            mStackRect.left += widthPadding;
-            mStackRect.right -= widthPadding;
-            mStackRect.bottom -= heightPadding;
-        } else {
-            mStackRect.inset(widthPadding, heightPadding);
-        }
-        mStackRectSansPeek.set(mStackRect);
-        mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
-
-        // Compute the task rect
-        int size = mStackRect.width();
-        int left = mStackRect.left + (mStackRect.width() - size) / 2;
-        mTaskRect.set(left, mStackRectSansPeek.top,
-                left + size, mStackRectSansPeek.top + size);
+        // Compute the rects in the stack algorithm
+        mStackAlgorithm.computeRects(width, height, insetLeft, insetBottom);
 
         // Update the scroll bounds
         updateMinMaxScroll(false);
@@ -707,17 +683,16 @@
         }
 
         // 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) {
-            Console.log("  [TaskStack|fullRect] " + mRect);
-            Console.log("  [TaskStack|stackRect] " + mStackRect);
-            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
-            Console.log("  [TaskStack|taskRect] " + mTaskRect);
+            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
+            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
+            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
+            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
         }
 
         // If this is the first layout, then scroll to the front of the stack and synchronize the
@@ -732,8 +707,8 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
-            t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
+            t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -753,65 +728,94 @@
 
         // Debug logging
         if (Constants.Log.UI.MeasureAndLayout) {
-            Console.log("  [TaskStack|fullRect] " + mRect);
-            Console.log("  [TaskStack|stackRect] " + mStackRect);
-            Console.log("  [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
-            Console.log("  [TaskStack|taskRect] " + mTaskRect);
+            Console.log("  [TaskStack|fullRect] " + mStackAlgorithm.mRect);
+            Console.log("  [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
+            Console.log("  [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
+            Console.log("  [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
         }
 
         // Layout each of the children
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
-            t.layout(mTaskRect.left, mStackRectSansPeek.top,
-                    mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
+            t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
+                    mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
+                    mStackAlgorithm.mTaskRect.height());
         }
 
         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;
 
+            // Start dozing
+            mUIDozeTrigger.startDozing();
+
+            // Prepare the first view for its enter animation
+            int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
+            int offscreenY = mStackAlgorithm.mRect.bottom -
+                    (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
+            for (int i = childCount - 1; i >= 0; i--) {
+                TaskView tv = (TaskView) getChildAt(i);
+                tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
+                        offscreenY, mStackAlgorithm.mTaskRect);
+            }
+
             // If the enter animation started already and we haven't completed a layout yet, do the
             // enter animation now
             if (mStartEnterAnimationRequestedAfterLayout) {
-                startOnEnterAnimation();
+                startEnterRecentsAnimation(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 startEnterRecentsAnimation(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
+        TaskViewTransform transform = mStackAlgorithm.getStackTransform(mStack.getTaskCount() - 1,
+                getInitialStackScroll());
+        ctx.taskRect = transform.rect;
+        ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            TaskView tv = (TaskView) getChildAt(i);
+            ctx.stackViewIndex = i;
+            ctx.stackViewCount = childCount;
+            ctx.isFrontMost = (i == (getChildCount() - 1));
+            ctx.transform = mStackAlgorithm.getStackTransform(
+                    mStack.indexOfTask(tv.getTask()), getStackScroll());
+            tv.startEnterRecentsAnimation(ctx);
         }
     }
 
+    /** Requests this task stacks to start it's exit-recents animation. */
+    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Animate all the task views into view
+        ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
+                (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            tv.startExitToHomeAnimation(ctx);
+        }
+
+        // Add a runnable to the post animation ref counter to clear all the views
+        ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
+    }
+
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
@@ -822,6 +826,17 @@
         return isTransformedTouchPointInView(x, y, child, null);
     }
 
+    /** Pokes the dozer on user interaction. */
+    void onUserInteraction() {
+        // Poke the doze trigger if it is dozing
+        mUIDozeTrigger.poke();
+    }
+
+    /** Disables handling touch on this task view. */
+    void setTouchOnTaskView(TaskView tv, boolean enabled) {
+        tv.setOnClickListener(enabled ? this : null);
+    }
+
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -833,18 +848,17 @@
     public void onStackTaskRemoved(TaskStack stack, Task t) {
         // Remove the view associated with this task, we can't rely on updateTransforms
         // to work here because the task is no longer in the list
-        int childCount = getChildCount();
-        for (int i = childCount - 1; i >= 0; i--) {
-            TaskView tv = (TaskView) getChildAt(i);
-            if (tv.getTask() == t) {
-                mViewPool.returnViewToPool(tv);
-                break;
-            }
+        TaskView tv = getChildViewForTask(t);
+        if (tv != null) {
+            mViewPool.returnViewToPool(tv);
         }
 
+        // Notify the callback that we've removed the task and it can clean up after it
+        mCb.onTaskRemoved(t);
+
         // Update the min/max scroll and animate other task views into their new positions
         updateMinMaxScroll(true);
-        int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
+        int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
         requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
 
         // If there are no remaining tasks, then either unfilter the current stack, or just close
@@ -856,153 +870,11 @@
                 shouldFinishActivity = (mStack.getTaskCount() == 0);
             }
             if (shouldFinishActivity) {
-                Activity activity = (Activity) getContext();
-                activity.finish();
+                mCb.onLastTaskRemoved();
             }
         }
     }
 
-    /**
-     * Creates the animations for all the children views that need to be removed or to move views
-     * to their un/filtered position when we are un/filtering a stack, and returns the duration
-     * for these animations.
-     */
-    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
-                        ArrayList<TaskViewTransform> curTaskTransforms,
-                        ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
-                        HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                        ArrayList<TaskView> childrenToRemoveOut,
-                        RecentsConfiguration config) {
-        // 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;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            TaskView tv = (TaskView) getChildAt(i);
-            Task task = tv.getTask();
-            int taskIndex = tasks.indexOf(task);
-            TaskViewTransform toTransform;
-
-            // If the view is no longer visible, then we should just animate it out
-            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
-            if (willBeInvisible) {
-                if (taskIndex < 0) {
-                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
-                } else {
-                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
-                }
-                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
-                childrenToRemoveOut.add(tv);
-            } else {
-                toTransform = taskTransforms.get(taskIndex);
-                // Use the movement of the visible views to calculate the duration of the animation
-                movement = Math.max(movement, Math.abs(toTransform.translationY -
-                        (int) tv.getTranslationY()));
-            }
-            childViewTransformsOut.put(tv, new Pair(0, toTransform));
-        }
-        return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringCurrentViewsMinAnimDuration);
-    }
-
-    /**
-     * Creates the animations for all the children views that need to be animated in when we are
-     * un/filtering a stack, and returns the duration for these animations.
-     */
-    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
-                         ArrayList<TaskViewTransform> taskTransforms,
-                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                         RecentsConfiguration config) {
-        int offset = 0;
-        int movement = 0;
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-            TaskViewTransform toTransform = taskTransforms.get(i);
-            if (toTransform.visible) {
-                TaskView tv = getChildViewForTask(task);
-                if (tv == null) {
-                    // For views that are not already visible, animate them in
-                    tv = mViewPool.pickUpViewFromPool(task, task);
-
-                    // Compose a new transform to fade and slide the new task in
-                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
-                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
-                    tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
-
-                    int startDelay = offset *
-                            Constants.Values.TaskStackView.FilterStartDelay;
-                    childViewTransformsOut.put(tv, new Pair(startDelay, toTransform));
-
-                    // Use the movement of the new views to calculate the duration of the animation
-                    movement = Math.max(movement,
-                            Math.abs(toTransform.translationY - fromTransform.translationY));
-                    offset++;
-                }
-            }
-        }
-        return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringNewViewsMinAnimDuration);
-    }
-
-    /** Orchestrates the animations of the current child views and any new views. */
-    void doFilteringAnimation(ArrayList<Task> curTasks,
-                              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);
-
-        // 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);
-            duration = Math.max(duration, inDuration);
-        }
-
-        // Animate all the views to their final transforms
-        for (final TaskView tv : childViewTransforms.keySet()) {
-            Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
-            tv.animate().cancel();
-            tv.animate()
-                    .setStartDelay(t.first)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            childViewTransforms.remove(tv);
-                            if (childViewTransforms.isEmpty()) {
-                                // Return all the removed children to the view pool
-                                for (TaskView tv : childrenToRemove) {
-                                    mViewPool.returnViewToPool(tv);
-                                }
-
-                                if (!unifyNewViewAnimation) {
-                                    // For views that are not already visible, animate them in
-                                    childViewTransforms.clear();
-                                    int duration = getEnterTransformsForFilterAnimation(tasks,
-                                            taskTransforms, childViewTransforms, config);
-                                    for (final TaskView tv : childViewTransforms.keySet()) {
-                                        Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
-                                        tv.animate().setStartDelay(t.first);
-                                        tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
-                                    }
-                                }
-                            }
-                        }
-                    });
-            tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
-        }
-    }
-
     @Override
     public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
                                 Task filteredTask) {
@@ -1015,7 +887,7 @@
 
         // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
         updateMinMaxScroll(false);
-        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+        float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
         setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
         boundScrollRaw();
 
@@ -1025,7 +897,10 @@
                 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
 
         // Animate
-        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+
+        // Notify any callbacks
+        mCb.onTaskStackFilterTriggered();
     }
 
     @Override
@@ -1045,10 +920,13 @@
                 getStackTransforms(tasks, getStackScroll(), null, true);
 
         // Animate
-        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+        mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
 
         // Clear the saved vars
         mStashedScroll = 0;
+
+        // Notify any callbacks
+        mCb.onTaskStackUnfilterTriggered();
     }
 
     /**** ViewPoolConsumer Implementation ****/
@@ -1064,53 +942,58 @@
     @Override
     public void prepareViewToEnterPool(TaskView tv) {
         Task task = tv.getTask();
-        tv.resetViewProperties();
         if (Console.Enabled) {
             Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
                     tv.getTask() + " tv: " + tv);
         }
 
         // Report that this tasks's data is no longer being used
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        loader.unloadTaskData(task);
+        RecentsTaskLoader.getInstance().unloadTaskData(task);
 
         // Detach the view from the hierarchy
         detachViewFromParent(tv);
 
-        // Disable hw layers on this view
+        // Disable HW layers
         tv.disableHwLayers();
+
+        // Reset the view properties
+        tv.resetViewProperties();
     }
 
     @Override
-    public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
+    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
         if (Console.Enabled) {
             Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
                     "isNewView: " + isNewView);
         }
 
-        // Setup and attach the view to the window
-        Task task = prepareData;
-        // We try and rebind the task (this MUST be done before the task filled)
+        // Rebind the task and request that this task's data be filled into the TaskView
         tv.onTaskBound(task);
-        // Request that this tasks's data be filled
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        loader.loadTaskData(task);
-
-        // Find the index where this task should be placed in the children
-        int insertIndex = -1;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            Task tvTask = ((TaskView) getChildAt(i)).getTask();
-            if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
-                insertIndex = i;
-                break;
-            }
-        }
+        RecentsTaskLoader.getInstance().loadTaskData(task);
 
         // Sanity check, the task view should always be clipping against the stack at this point,
         // but just in case, re-enable it here
         tv.setClipViewInStack(true);
 
+        // If the doze trigger has already fired, then update the state for this task view
+        if (mUIDozeTrigger.hasTriggered()) {
+            tv.setNoUserInteractionState();
+        }
+
+        // Find the index where this task should be placed in the stack
+        int insertIndex = -1;
+        int taskIndex = mStack.indexOfTask(task);
+        if (taskIndex != -1) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                Task tvTask = ((TaskView) getChildAt(i)).getTask();
+                if (taskIndex < mStack.indexOfTask(tvTask)) {
+                    insertIndex = i;
+                    break;
+                }
+            }
+        }
+
         // Add/attach the view to the hierarchy
         if (Console.Enabled) {
             Console.log(Constants.Log.ViewPool.PoolCallbacks, "  [TaskStackView|insertIndex]",
@@ -1120,14 +1003,14 @@
             addView(tv, insertIndex);
 
             // Set the callbacks and listeners for this new view
-            tv.setOnClickListener(this);
+            setTouchOnTaskView(tv, true);
             tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
         }
 
         // Enable hw layers on this view if hw layers are enabled on the stack
-        if (mHwLayersRefCount > 0) {
+        if (mHwLayersTrigger.getCount() > 0) {
             tv.enableHwLayers();
         }
     }
@@ -1172,8 +1055,6 @@
         Task task = tv.getTask();
         // Remove the task from the view
         mStack.removeTask(task);
-        // Notify the callback that we've removed the task and it can clean up after it
-        mCb.onTaskRemoved(task);
     }
 
     /**** View.OnClickListener Implementation ****/
@@ -1187,6 +1068,9 @@
                     task + " cb: " + mCb);
         }
 
+        // Cancel any doze triggers
+        mUIDozeTrigger.stopDozing();
+
         if (mCb != null) {
             mCb.onTaskLaunched(this, tv, mStack, task);
         }
@@ -1196,7 +1080,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--) {
@@ -1205,7 +1088,7 @@
                 TaskView tv = getChildViewForTask(t);
                 if (tv != null) {
                     // For visible children, defer removing the task until after the animation
-                    tv.animateRemoval(new Runnable() {
+                    tv.startDeleteTaskAnimation(new Runnable() {
                         @Override
                         public void run() {
                             mStack.removeTask(t);
@@ -1218,393 +1101,4 @@
             }
         }
     }
-}
-
-/* Handles touch events */
-class TaskStackViewTouchHandler implements SwipeHelper.Callback {
-    static int INACTIVE_POINTER_ID = -1;
-
-    TaskStackView mSv;
-    VelocityTracker mVelocityTracker;
-
-    boolean mIsScrolling;
-
-    int mInitialMotionX, mInitialMotionY;
-    int mLastMotionX, mLastMotionY;
-    int mActivePointerId = INACTIVE_POINTER_ID;
-    TaskView mActiveTaskView = null;
-
-    int mTotalScrollMotion;
-    int mMinimumVelocity;
-    int mMaximumVelocity;
-    // The scroll touch slop is used to calculate when we start scrolling
-    int mScrollTouchSlop;
-    // The page touch slop is used to calculate when we start swiping
-    float mPagingTouchSlop;
-
-    SwipeHelper mSwipeHelper;
-    boolean mInterceptedBySwipeHelper;
-
-    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
-        ViewConfiguration configuration = ViewConfiguration.get(context);
-        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
-        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mScrollTouchSlop = configuration.getScaledTouchSlop();
-        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
-        mSv = sv;
-
-
-        float densityScale = context.getResources().getDisplayMetrics().density;
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
-        mSwipeHelper.setMinAlpha(1f);
-    }
-
-    /** Velocity tracker helpers */
-    void initOrResetVelocityTracker() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        } else {
-            mVelocityTracker.clear();
-        }
-    }
-    void initVelocityTrackerIfNotExists() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-    }
-    void recycleVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    /** Returns the view at the specified coordinates */
-    TaskView findViewAtPoint(int x, int y) {
-        int childCount = mSv.getChildCount();
-        for (int i = childCount - 1; i >= 0; i--) {
-            TaskView tv = (TaskView) mSv.getChildAt(i);
-            if (tv.getVisibility() == View.VISIBLE) {
-                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
-                    return tv;
-                }
-            }
-        }
-        return null;
-    }
-
-    /** Touch preprocessing for handling below */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[TaskStackViewTouchHandler|interceptTouchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
-
-        // Return early if we have no children
-        boolean hasChildren = (mSv.getChildCount() > 0);
-        if (!hasChildren) {
-            return false;
-        }
-
-        // Pass through to swipe helper if we are swiping
-        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
-        if (mInterceptedBySwipeHelper) {
-            return true;
-        }
-
-        boolean wasScrolling = !mSv.mScroller.isFinished() ||
-                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
-        int action = ev.getAction();
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                // Save the touch down info
-                mInitialMotionX = mLastMotionX = (int) ev.getX();
-                mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mActivePointerId = ev.getPointerId(0);
-                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
-                // Stop the current scroll if it is still flinging
-                mSv.abortScroller();
-                mSv.abortBoundScrollAnimation();
-                // Initialize the velocity tracker
-                initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(ev);
-                // Check if the scroller is finished yet
-                mIsScrolling = !mSv.mScroller.isFinished();
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                int y = (int) ev.getY(activePointerIndex);
-                int x = (int) ev.getX(activePointerIndex);
-                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
-                    // Save the touch move info
-                    mIsScrolling = true;
-                    // Initialize the velocity tracker if necessary
-                    initVelocityTrackerIfNotExists();
-                    mVelocityTracker.addMovement(ev);
-                    // Disallow parents from intercepting touch events
-                    final ViewParent parent = mSv.getParent();
-                    if (parent != null) {
-                        parent.requestDisallowInterceptTouchEvent(true);
-                    }
-                    // Enable HW layers
-                    mSv.addHwLayersRefCount("stackScroll");
-                }
-
-                mLastMotionX = x;
-                mLastMotionY = y;
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP: {
-                // Animate the scroll back if we've cancelled
-                mSv.animateBoundScroll();
-                // Disable HW layers
-                if (mIsScrolling) {
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
-                // Reset the drag state and the velocity tracker
-                mIsScrolling = false;
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mActiveTaskView = null;
-                mTotalScrollMotion = 0;
-                recycleVelocityTracker();
-                break;
-            }
-        }
-
-        return wasScrolling || mIsScrolling;
-    }
-
-    /** Handles touch events once we have intercepted them */
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.TouchEvents,
-                    "[TaskStackViewTouchHandler|touchEvent]",
-                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
-        }
-
-        // Short circuit if we have no children
-        boolean hasChildren = (mSv.getChildCount() > 0);
-        if (!hasChildren) {
-            return false;
-        }
-
-        // Pass through to swipe helper if we are swiping
-        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
-            return true;
-        }
-
-        // Update the velocity tracker
-        initVelocityTrackerIfNotExists();
-        mVelocityTracker.addMovement(ev);
-
-        int action = ev.getAction();
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                // Save the touch down info
-                mInitialMotionX = mLastMotionX = (int) ev.getX();
-                mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mActivePointerId = ev.getPointerId(0);
-                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
-                // Stop the current scroll if it is still flinging
-                mSv.abortScroller();
-                mSv.abortBoundScrollAnimation();
-                // Initialize the velocity tracker
-                initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(ev);
-                // Disallow parents from intercepting touch events
-                final ViewParent parent = mSv.getParent();
-                if (parent != null) {
-                    parent.requestDisallowInterceptTouchEvent(true);
-                }
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                final int index = ev.getActionIndex();
-                mActivePointerId = ev.getPointerId(index);
-                mLastMotionX = (int) ev.getX(index);
-                mLastMotionY = (int) ev.getY(index);
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                int x = (int) ev.getX(activePointerIndex);
-                int y = (int) ev.getY(activePointerIndex);
-                int yTotal = Math.abs(y - mInitialMotionY);
-                int deltaY = mLastMotionY - y;
-                if (!mIsScrolling) {
-                    if (yTotal > mScrollTouchSlop) {
-                        mIsScrolling = true;
-                        // Initialize the velocity tracker
-                        initOrResetVelocityTracker();
-                        mVelocityTracker.addMovement(ev);
-                        // Disallow parents from intercepting touch events
-                        final ViewParent parent = mSv.getParent();
-                        if (parent != null) {
-                            parent.requestDisallowInterceptTouchEvent(true);
-                        }
-                        // Enable HW layers
-                        mSv.addHwLayersRefCount("stackScroll");
-                    }
-                }
-                if (mIsScrolling) {
-                    int curStackScroll = mSv.getStackScroll();
-                    int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
-                    if (overScrollAmount != 0) {
-                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
-                        // relative to how close we are to the max overscroll
-                        float maxOverScroll = mSv.mTaskRect.height() / 3f;
-                        deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
-                                / maxOverScroll)));
-                    }
-                    mSv.setStackScroll(curStackScroll + deltaY);
-                    if (mSv.isScrollOutOfBounds()) {
-                        mVelocityTracker.clear();
-                    }
-                }
-                mLastMotionX = x;
-                mLastMotionY = y;
-                mTotalScrollMotion += Math.abs(deltaY);
-                break;
-            }
-            case MotionEvent.ACTION_UP: {
-                final VelocityTracker velocityTracker = mVelocityTracker;
-                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
-                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
-                    // Enable HW layers on the stack
-                    mSv.addHwLayersRefCount("flingScroll");
-                    // XXX: Make this animation a function of the velocity AND distance
-                    int overscrollRange = (int) (Math.min(1f,
-                            Math.abs((float) velocity / mMaximumVelocity)) *
-                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
-
-                    if (Console.Enabled) {
-                        Console.log(Constants.Log.UI.TouchEvents,
-                                "[TaskStackViewTouchHandler|fling]",
-                                "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
-                                        " maxVelocity: " + mMaximumVelocity +
-                                        " overscrollRange: " + overscrollRange,
-                                Console.AnsiGreen);
-                    }
-
-                    // Fling scroll
-                    mSv.mScroller.fling(0, mSv.getStackScroll(),
-                            0, -velocity,
-                            0, 0,
-                            mSv.mMinScroll, mSv.mMaxScroll,
-                            0, overscrollRange);
-                    // Invalidate to kick off computeScroll
-                    mSv.invalidate();
-                } else if (mSv.isScrollOutOfBounds()) {
-                    // Animate the scroll back into bounds
-                    // XXX: Make this animation a function of the velocity OR distance
-                    mSv.animateBoundScroll();
-                }
-
-                if (mIsScrolling) {
-                    // Disable HW layers
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mIsScrolling = false;
-                mTotalScrollMotion = 0;
-                recycleVelocityTracker();
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                int pointerIndex = ev.getActionIndex();
-                int pointerId = ev.getPointerId(pointerIndex);
-                if (pointerId == mActivePointerId) {
-                    // Select a new active pointer id and reset the motion state
-                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
-                    mActivePointerId = ev.getPointerId(newPointerIndex);
-                    mLastMotionX = (int) ev.getX(newPointerIndex);
-                    mLastMotionY = (int) ev.getY(newPointerIndex);
-                    mVelocityTracker.clear();
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL: {
-                if (mIsScrolling) {
-                    // Disable HW layers
-                    mSv.decHwLayersRefCount("stackScroll");
-                }
-                if (mSv.isScrollOutOfBounds()) {
-                    // Animate the scroll back into bounds
-                    // XXX: Make this animation a function of the velocity OR distance
-                    mSv.animateBoundScroll();
-                }
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mIsScrolling = false;
-                mTotalScrollMotion = 0;
-                recycleVelocityTracker();
-                break;
-            }
-        }
-        return true;
-    }
-
-    /**** SwipeHelper Implementation ****/
-
-    @Override
-    public View getChildAtPosition(MotionEvent ev) {
-        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
-    }
-
-    @Override
-    public boolean canChildBeDismissed(View v) {
-        return true;
-    }
-
-    @Override
-    public void onBeginDrag(View v) {
-        // Enable HW layers
-        mSv.addHwLayersRefCount("swipeBegin");
-        // Disable clipping with the stack while we are swiping
-        TaskView tv = (TaskView) v;
-        tv.setClipViewInStack(false);
-        // Disallow parents from intercepting touch events
-        final ViewParent parent = mSv.getParent();
-        if (parent != null) {
-            parent.requestDisallowInterceptTouchEvent(true);
-        }
-    }
-
-    @Override
-    public void onSwipeChanged(View v, float delta) {
-        // Do nothing
-    }
-
-    @Override
-    public void onChildDismissed(View v) {
-        TaskView tv = (TaskView) v;
-        mSv.onTaskDismissed(tv);
-
-        // Re-enable clipping with the stack (we will reuse this view)
-        tv.setClipViewInStack(true);
-
-        // Disable HW layers
-        mSv.decHwLayersRefCount("swipeComplete");
-    }
-
-    @Override
-    public void onSnapBackCompleted(View v) {
-        // Re-enable clipping with the stack
-        TaskView tv = (TaskView) v;
-        tv.setClipViewInStack(true);
-    }
-
-    @Override
-    public void onDragCancelled(View v) {
-        // Disable HW layers
-        mSv.decHwLayersRefCount("swipeCancelled");
-    }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
new file mode 100644
index 0000000..9cd5ae4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
@@ -0,0 +1,176 @@
+/*
+ * 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 com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/* The layout logic for a TaskStackView */
+public class TaskStackViewFilterAlgorithm {
+
+    RecentsConfiguration mConfig;
+    TaskStackView mStackView;
+    ViewPool<TaskView, Task> mViewPool;
+
+    public TaskStackViewFilterAlgorithm(RecentsConfiguration config, TaskStackView stackView,
+                                        ViewPool<TaskView, Task> viewPool) {
+        mConfig = config;
+        mStackView = stackView;
+        mViewPool = viewPool;
+    }
+
+    /** Orchestrates the animations of the current child views and any new views. */
+    void startFilteringAnimation(ArrayList<Task> curTasks,
+                                 ArrayList<TaskViewTransform> curTaskTransforms,
+                                 final ArrayList<Task> tasks,
+                                 final ArrayList<TaskViewTransform> taskTransforms) {
+        // 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, TaskViewTransform> childViewTransforms =
+                new HashMap<TaskView, TaskViewTransform>();
+        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
+                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);
+            duration = Math.max(duration, inDuration);
+        }
+
+        // Animate all the views to their final transforms
+        for (final TaskView tv : childViewTransforms.keySet()) {
+            TaskViewTransform t = childViewTransforms.get(tv);
+            tv.animate().cancel();
+            tv.animate()
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            childViewTransforms.remove(tv);
+                            if (childViewTransforms.isEmpty()) {
+                                // Return all the removed children to the view pool
+                                for (TaskView tv : childrenToRemove) {
+                                    mViewPool.returnViewToPool(tv);
+                                }
+
+                                if (!unifyNewViewAnimation) {
+                                    // For views that are not already visible, animate them in
+                                    childViewTransforms.clear();
+                                    int duration = getEnterTransformsForFilterAnimation(tasks,
+                                            taskTransforms, childViewTransforms);
+                                    for (final TaskView tv : childViewTransforms.keySet()) {
+                                        TaskViewTransform t = childViewTransforms.get(tv);
+                                        tv.updateViewPropertiesToTaskTransform(t, duration);
+                                    }
+                                }
+                            }
+                        }
+                    });
+            tv.updateViewPropertiesToTaskTransform(t, duration);
+        }
+    }
+
+    /**
+     * Creates the animations for all the children views that need to be animated in when we are
+     * un/filtering a stack, and returns the duration for these animations.
+     */
+    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
+                                             ArrayList<TaskViewTransform> taskTransforms,
+                                             HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
+        int offset = 0;
+        int movement = 0;
+        int taskCount = tasks.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            Task task = tasks.get(i);
+            TaskViewTransform toTransform = taskTransforms.get(i);
+            if (toTransform.visible) {
+                TaskView tv = mStackView.getChildViewForTask(task);
+                if (tv == null) {
+                    // For views that are not already visible, animate them in
+                    tv = mViewPool.pickUpViewFromPool(task, task);
+
+                    // Compose a new transform to fade and slide the new task in
+                    TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
+                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
+                    tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
+
+                    toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+                    childViewTransformsOut.put(tv, toTransform);
+
+                    // Use the movement of the new views to calculate the duration of the animation
+                    movement = Math.max(movement,
+                            Math.abs(toTransform.translationY - fromTransform.translationY));
+                    offset++;
+                }
+            }
+        }
+        return mConfig.filteringNewViewsAnimDuration;
+    }
+
+    /**
+     * Creates the animations for all the children views that need to be removed or to move views
+     * to their un/filtered position when we are un/filtering a stack, and returns the duration
+     * for these animations.
+     */
+    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
+                                            ArrayList<TaskViewTransform> curTaskTransforms,
+                                            ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
+                                            HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
+                                            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 offset = 0;
+        int movement = 0;
+        int childCount = mStackView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) mStackView.getChildAt(i);
+            Task task = tv.getTask();
+            int taskIndex = tasks.indexOf(task);
+            TaskViewTransform toTransform;
+
+            // If the view is no longer visible, then we should just animate it out
+            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
+            if (willBeInvisible) {
+                if (taskIndex < 0) {
+                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
+                } else {
+                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
+                }
+                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
+                childrenToRemoveOut.add(tv);
+            } else {
+                toTransform = taskTransforms.get(taskIndex);
+                // Use the movement of the visible views to calculate the duration of the animation
+                movement = Math.max(movement, Math.abs(toTransform.translationY -
+                        (int) tv.getTranslationY()));
+            }
+
+            toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+            childViewTransformsOut.put(tv, toTransform);
+            offset++;
+        }
+        return mConfig.filteringCurrentViewsAnimDuration;
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
new file mode 100644
index 0000000..daa18bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -0,0 +1,173 @@
+/*
+ * 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.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Utilities;
+
+/* The layout logic for a TaskStackView */
+public class TaskStackViewLayoutAlgorithm {
+
+    // These are all going to change
+    static final float StackOverlapPct = 0.65f; // The overlap height relative to the task height
+    static final float StackPeekHeightPct = 0.1f; // The height of the peek space relative to the stack height
+    static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
+    static final int StackPeekNumCards = 3; // The number of cards we see in the peek space
+
+    RecentsConfiguration mConfig;
+
+    // The various rects that define the stack view
+    Rect mRect = new Rect();
+    Rect mStackRect = new Rect();
+    Rect mStackRectSansPeek = new Rect();
+    Rect mTaskRect = new Rect();
+
+    // The min/max scroll
+    int mMinScroll;
+    int mMaxScroll;
+
+    public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
+        mConfig = config;
+    }
+
+    /** Computes the stack and task rects */
+    public void computeRects(int width, int height, int insetLeft, int insetBottom) {
+        // 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
+
+        // Compute the stack rects
+        mRect.set(0, 0, width, height);
+        mStackRect.set(mRect);
+        mStackRect.left += insetLeft;
+        mStackRect.bottom -= insetBottom;
+
+        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
+        int heightPadding = mConfig.taskStackTopPaddingPx;
+        if (Constants.DebugFlags.App.EnableSearchLayout) {
+            mStackRect.top += heightPadding;
+            mStackRect.left += widthPadding;
+            mStackRect.right -= widthPadding;
+            mStackRect.bottom -= heightPadding;
+        } else {
+            mStackRect.inset(widthPadding, heightPadding);
+        }
+        mStackRectSansPeek.set(mStackRect);
+        mStackRectSansPeek.top += StackPeekHeightPct * mStackRect.height();
+
+        // Compute the task rect
+        int size = mStackRect.width();
+        int left = mStackRect.left + (mStackRect.width() - size) / 2;
+        mTaskRect.set(left, mStackRectSansPeek.top,
+                left + size, mStackRectSansPeek.top + size);
+    }
+
+    void computeMinMaxScroll(int taskCount) {
+        // Compute the min and max scroll values
+        int numTasks = Math.max(1, taskCount);
+        int taskHeight = mTaskRect.height();
+        int stackHeight = mStackRectSansPeek.height();
+        int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(numTasks - 1);
+
+        if (numTasks <= 1) {
+            // If there is only one task, then center the task in the stack rect (sans peek)
+            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+        } else {
+            mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
+            mMaxScroll = maxScrollHeight - stackHeight;
+        }
+    }
+
+    /** Update/get the transform (creates a new TaskViewTransform) */
+    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
+        TaskViewTransform transform = new TaskViewTransform();
+        return getStackTransform(indexInStack, stackScroll, transform);
+    }
+
+    /** Update/get the transform */
+    public TaskViewTransform getStackTransform(int indexInStack, int stackScroll,
+                                               TaskViewTransform transformOut) {
+        // Return early if we have an invalid index
+        if (indexInStack < 0) {
+            transformOut.reset();
+            return transformOut;
+        }
+
+        // Map the items to an continuous position relative to the specified scroll
+        int numPeekCards = StackPeekNumCards;
+        float overlapHeight = StackOverlapPct * mTaskRect.height();
+        float peekHeight = StackPeekHeightPct * mStackRect.height();
+        float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
+        float boundedT = Math.max(t, -(numPeekCards + 1));
+
+        // Set the scale relative to its position
+        int numFrontScaledCards = 3;
+        float minScale = StackPeekMinScale;
+        float scaleRange = 1f - minScale;
+        float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
+        float scale = Math.max(minScale, Math.min(1f, minScale + 
+            ((boundedT + (numPeekCards + 1)) * scaleInc)));
+        float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
+        transformOut.scale = scale;
+
+        // Set the y translation
+        if (boundedT < 0f) {
+            transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
+                    numPeekCards) * peekHeight - scaleYOffset);
+        } else {
+            transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
+        }
+
+        // Set the z translation
+        int minZ = mConfig.taskViewTranslationZMinPx;
+        int incZ = mConfig.taskViewTranslationZIncrementPx;
+        transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
+
+        // Set the alphas
+        transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+
+        // Update the rect and visibility
+        transformOut.rect.set(mTaskRect);
+        if (t < -(numPeekCards + 1)) {
+            transformOut.visible = false;
+        } else {
+            transformOut.rect.offset(0, transformOut.translationY);
+            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+            transformOut.visible = Rect.intersects(mRect, transformOut.rect);
+        }
+        transformOut.t = t;
+        return transformOut;
+    }
+
+    /**
+     * Returns the overlap between one task and the next.
+     */
+    float getTaskOverlapHeight() {
+        return StackOverlapPct * mTaskRect.height();
+    }
+
+    /**
+     * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
+     * is not bounded)
+     */
+    int getStackScrollForTaskIndex(int i) {
+        return (int) (i * getTaskOverlapHeight());
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
new file mode 100644
index 0000000..51f994e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -0,0 +1,423 @@
+/*
+ * 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.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+
+/* Handles touch events for a TaskStackView. */
+class TaskStackViewTouchHandler implements SwipeHelper.Callback {
+    static int INACTIVE_POINTER_ID = -1;
+
+    TaskStackView mSv;
+    VelocityTracker mVelocityTracker;
+
+    boolean mIsScrolling;
+
+    int mInitialMotionX, mInitialMotionY;
+    int mLastMotionX, mLastMotionY;
+    int mActivePointerId = INACTIVE_POINTER_ID;
+    TaskView mActiveTaskView = null;
+
+    int mTotalScrollMotion;
+    int mMinimumVelocity;
+    int mMaximumVelocity;
+    // The scroll touch slop is used to calculate when we start scrolling
+    int mScrollTouchSlop;
+    // The page touch slop is used to calculate when we start swiping
+    float mPagingTouchSlop;
+
+    SwipeHelper mSwipeHelper;
+    boolean mInterceptedBySwipeHelper;
+
+    public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
+        ViewConfiguration configuration = ViewConfiguration.get(context);
+        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mScrollTouchSlop = configuration.getScaledTouchSlop();
+        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+        mSv = sv;
+
+
+        float densityScale = context.getResources().getDisplayMetrics().density;
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
+        mSwipeHelper.setMinAlpha(1f);
+    }
+
+    /** Velocity tracker helpers */
+    void initOrResetVelocityTracker() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        } else {
+            mVelocityTracker.clear();
+        }
+    }
+    void initVelocityTrackerIfNotExists() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+    }
+    void recycleVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    /** Returns the view at the specified coordinates */
+    TaskView findViewAtPoint(int x, int y) {
+        int childCount = mSv.getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            TaskView tv = (TaskView) mSv.getChildAt(i);
+            if (tv.getVisibility() == View.VISIBLE) {
+                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
+                    return tv;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** Touch preprocessing for handling below */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.TouchEvents,
+                    "[TaskStackViewTouchHandler|interceptTouchEvent]",
+                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+        }
+
+        // Return early if we have no children
+        boolean hasChildren = (mSv.getChildCount() > 0);
+        if (!hasChildren) {
+            return false;
+        }
+
+        // Pass through to swipe helper if we are swiping
+        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
+        if (mInterceptedBySwipeHelper) {
+            return true;
+        }
+
+        boolean wasScrolling = !mSv.mScroller.isFinished() ||
+                (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
+        int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                // Save the touch down info
+                mInitialMotionX = mLastMotionX = (int) ev.getX();
+                mInitialMotionY = mLastMotionY = (int) ev.getY();
+                mActivePointerId = ev.getPointerId(0);
+                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+                // Stop the current scroll if it is still flinging
+                mSv.abortScroller();
+                mSv.abortBoundScrollAnimation();
+                // Initialize the velocity tracker
+                initOrResetVelocityTracker();
+                mVelocityTracker.addMovement(ev);
+                // Check if the scroller is finished yet
+                mIsScrolling = !mSv.mScroller.isFinished();
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                int y = (int) ev.getY(activePointerIndex);
+                int x = (int) ev.getX(activePointerIndex);
+                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+                    // Save the touch move info
+                    mIsScrolling = true;
+                    // Initialize the velocity tracker if necessary
+                    initVelocityTrackerIfNotExists();
+                    mVelocityTracker.addMovement(ev);
+                    // Disallow parents from intercepting touch events
+                    final ViewParent parent = mSv.getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                    // Enable HW layers
+                    mSv.addHwLayersRefCount("stackScroll");
+                }
+
+                mLastMotionX = x;
+                mLastMotionY = y;
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP: {
+                // Animate the scroll back if we've cancelled
+                mSv.animateBoundScroll();
+                // Disable HW layers
+                if (mIsScrolling) {
+                    mSv.decHwLayersRefCount("stackScroll");
+                }
+                // Reset the drag state and the velocity tracker
+                mIsScrolling = false;
+                mActivePointerId = INACTIVE_POINTER_ID;
+                mActiveTaskView = null;
+                mTotalScrollMotion = 0;
+                recycleVelocityTracker();
+                break;
+            }
+        }
+
+        return wasScrolling || mIsScrolling;
+    }
+
+    /** Handles touch events once we have intercepted them */
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.TouchEvents,
+                    "[TaskStackViewTouchHandler|touchEvent]",
+                    Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+        }
+
+        // Short circuit if we have no children
+        boolean hasChildren = (mSv.getChildCount() > 0);
+        if (!hasChildren) {
+            return false;
+        }
+
+        // Pass through to swipe helper if we are swiping
+        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
+            return true;
+        }
+
+        // Update the velocity tracker
+        initVelocityTrackerIfNotExists();
+        mVelocityTracker.addMovement(ev);
+
+        int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                // Save the touch down info
+                mInitialMotionX = mLastMotionX = (int) ev.getX();
+                mInitialMotionY = mLastMotionY = (int) ev.getY();
+                mActivePointerId = ev.getPointerId(0);
+                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+                // Stop the current scroll if it is still flinging
+                mSv.abortScroller();
+                mSv.abortBoundScrollAnimation();
+                // Initialize the velocity tracker
+                initOrResetVelocityTracker();
+                mVelocityTracker.addMovement(ev);
+                // Disallow parents from intercepting touch events
+                final ViewParent parent = mSv.getParent();
+                if (parent != null) {
+                    parent.requestDisallowInterceptTouchEvent(true);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_DOWN: {
+                final int index = ev.getActionIndex();
+                mActivePointerId = ev.getPointerId(index);
+                mLastMotionX = (int) ev.getX(index);
+                mLastMotionY = (int) ev.getY(index);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                int x = (int) ev.getX(activePointerIndex);
+                int y = (int) ev.getY(activePointerIndex);
+                int yTotal = Math.abs(y - mInitialMotionY);
+                int deltaY = mLastMotionY - y;
+                if (!mIsScrolling) {
+                    if (yTotal > mScrollTouchSlop) {
+                        mIsScrolling = true;
+                        // Initialize the velocity tracker
+                        initOrResetVelocityTracker();
+                        mVelocityTracker.addMovement(ev);
+                        // Disallow parents from intercepting touch events
+                        final ViewParent parent = mSv.getParent();
+                        if (parent != null) {
+                            parent.requestDisallowInterceptTouchEvent(true);
+                        }
+                        // Enable HW layers
+                        mSv.addHwLayersRefCount("stackScroll");
+                    }
+                }
+                if (mIsScrolling) {
+                    int curStackScroll = mSv.getStackScroll();
+                    int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
+                    if (overScrollAmount != 0) {
+                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
+                        // relative to how close we are to the max overscroll
+                        float maxOverScroll = mSv.mStackAlgorithm.mTaskRect.height() / 3f;
+                        deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
+                                / maxOverScroll)));
+                    }
+                    mSv.setStackScroll(curStackScroll + deltaY);
+                    if (mSv.isScrollOutOfBounds()) {
+                        mVelocityTracker.clear();
+                    }
+                }
+                mLastMotionX = x;
+                mLastMotionY = y;
+                mTotalScrollMotion += Math.abs(deltaY);
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+
+                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
+                    // Enable HW layers on the stack
+                    mSv.addHwLayersRefCount("flingScroll");
+                    // XXX: Make this animation a function of the velocity AND distance
+                    int overscrollRange = (int) (Math.min(1f,
+                            Math.abs((float) velocity / mMaximumVelocity)) *
+                            Constants.Values.TaskStackView.TaskStackOverscrollRange);
+
+                    if (Console.Enabled) {
+                        Console.log(Constants.Log.UI.TouchEvents,
+                                "[TaskStackViewTouchHandler|fling]",
+                                "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
+                                        " maxVelocity: " + mMaximumVelocity +
+                                        " overscrollRange: " + overscrollRange,
+                                Console.AnsiGreen);
+                    }
+
+                    // Fling scroll
+                    mSv.mScroller.fling(0, mSv.getStackScroll(),
+                            0, -velocity,
+                            0, 0,
+                            mSv.mMinScroll, mSv.mMaxScroll,
+                            0, overscrollRange);
+                    // Invalidate to kick off computeScroll
+                    mSv.invalidate(mSv.mStackAlgorithm.mStackRect);
+                } else if (mSv.isScrollOutOfBounds()) {
+                    // Animate the scroll back into bounds
+                    // XXX: Make this animation a function of the velocity OR distance
+                    mSv.animateBoundScroll();
+                }
+
+                if (mIsScrolling) {
+                    // Disable HW layers
+                    mSv.decHwLayersRefCount("stackScroll");
+                }
+                mActivePointerId = INACTIVE_POINTER_ID;
+                mIsScrolling = false;
+                mTotalScrollMotion = 0;
+                recycleVelocityTracker();
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                int pointerIndex = ev.getActionIndex();
+                int pointerId = ev.getPointerId(pointerIndex);
+                if (pointerId == mActivePointerId) {
+                    // Select a new active pointer id and reset the motion state
+                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+                    mActivePointerId = ev.getPointerId(newPointerIndex);
+                    mLastMotionX = (int) ev.getX(newPointerIndex);
+                    mLastMotionY = (int) ev.getY(newPointerIndex);
+                    mVelocityTracker.clear();
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                if (mIsScrolling) {
+                    // Disable HW layers
+                    mSv.decHwLayersRefCount("stackScroll");
+                }
+                if (mSv.isScrollOutOfBounds()) {
+                    // Animate the scroll back into bounds
+                    // XXX: Make this animation a function of the velocity OR distance
+                    mSv.animateBoundScroll();
+                }
+                mActivePointerId = INACTIVE_POINTER_ID;
+                mIsScrolling = false;
+                mTotalScrollMotion = 0;
+                recycleVelocityTracker();
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**** SwipeHelper Implementation ****/
+
+    @Override
+    public View getChildAtPosition(MotionEvent ev) {
+        return findViewAtPoint((int) ev.getX(), (int) ev.getY());
+    }
+
+    @Override
+    public boolean canChildBeDismissed(View v) {
+        return true;
+    }
+
+    @Override
+    public void onBeginDrag(View v) {
+        TaskView tv = (TaskView) v;
+        // Disable clipping with the stack while we are swiping
+        tv.setClipViewInStack(false);
+        // Enable HW layers on that task
+        tv.enableHwLayers();
+        // Disallow touch events from this task view
+        mSv.setTouchOnTaskView(tv, false);
+        // Disallow parents from intercepting touch events
+        final ViewParent parent = mSv.getParent();
+        if (parent != null) {
+            parent.requestDisallowInterceptTouchEvent(true);
+        }
+    }
+
+    @Override
+    public void onSwipeChanged(View v, float delta) {
+        // Do nothing
+    }
+
+    @Override
+    public void onChildDismissed(View v) {
+        TaskView tv = (TaskView) v;
+        // Disable HW layers on that task
+        if (mSv.mHwLayersTrigger.getCount() == 0) {
+            tv.disableHwLayers();
+        }
+        // Re-enable clipping with the stack (we will reuse this view)
+        tv.setClipViewInStack(true);
+        // Remove the task view from the stack
+        mSv.onTaskDismissed(tv);
+    }
+
+    @Override
+    public void onSnapBackCompleted(View v) {
+        onDragCancelled(v);
+    }
+
+    @Override
+    public void onDragCancelled(View v) {
+        TaskView tv = (TaskView) v;
+        // Disable HW layers on that task
+        if (mSv.mHwLayersTrigger.getCount() == 0) {
+            tv.disableHwLayers();
+        }
+        // Re-enable clipping with the stack
+        tv.setClipViewInStack(true);
+        // Re-enable touch events from this task view
+        mSv.setTouchOnTaskView(tv, true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
index 8a9250a..636746d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -17,15 +17,23 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 import com.android.systemui.recents.model.Task;
 
 
 /** The task thumbnail view */
 public class TaskThumbnailView extends ImageView {
+
     Task mTask;
 
+    // Task bar clipping
+    Rect mClipRect;
+    boolean mClipTaskBar = true;
+
     public TaskThumbnailView(Context context) {
         this(context, null);
     }
@@ -43,14 +51,42 @@
         setScaleType(ScaleType.FIT_XY);
     }
 
+    @Override
+    public void draw(Canvas canvas) {
+        if (mClipTaskBar && (mClipRect != null)) {
+            int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+            canvas.clipRect(mClipRect);
+            super.draw(canvas);
+            canvas.restoreToCount(restoreCount);
+        } else {
+            super.draw(canvas);
+        }
+    }
+
+    /** Updates the clip rect based on the given task bar. */
+    void updateTaskBarClip(View taskBar) {
+        // If mClipTaskBar is unset first, then we don't bother setting mTaskBar
+        if (mClipTaskBar) {
+            int top = (int) Math.max(0, taskBar.getTranslationY() +
+                    taskBar.getMeasuredHeight() - 1);
+            mClipRect = new Rect(0, top, getMeasuredWidth(), getMeasuredHeight());
+            invalidate(0, 0, taskBar.getMeasuredWidth(), taskBar.getMeasuredHeight() + 1);
+        }
+    }
+
+    /** Disables the task bar clipping. */
+    void disableClipTaskBarView() {
+        mClipTaskBar = false;
+        if (mClipRect != null) {
+            invalidate(0, 0, mClipRect.width(), mClipRect.top);
+        }
+    }
+
     /** Binds the thumbnail view to the task */
-    void rebindToTask(Task t, boolean animate) {
+    void rebindToTask(Task t) {
         mTask = t;
         if (t.thumbnail != null) {
             setImageBitmap(t.thumbnail);
-            if (animate) {
-                // XXX: Investigate how expensive it will be to create a second bitmap and crossfade
-            }
         }
     }
 
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..b1fc700 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -16,11 +16,13 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Outline;
+import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -28,9 +30,11 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 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 +49,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 +63,34 @@
     boolean mClipViewInStack;
     Point mLastTouchDown = new Point();
     Path mRoundedRectClipPath = new Path();
+    Rect mTmpRect = new Rect();
+    Paint mLayerPaint = new Paint();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
     TaskViewCallbacks mCb;
 
+    // Optimizations
+    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    updateDimOverlayFromScale();
+                }
+            };
+    Runnable mEnableThumbnailClip = new Runnable() {
+        @Override
+        public void run() {
+            mThumbnailView.updateTaskBarClip(mBarView);
+        }
+    };
+    Runnable mDisableThumbnailClip = new Runnable() {
+        @Override
+        public void run() {
+            mThumbnailView.disableClipTaskBarView();
+        }
+    };
+
 
     public TaskView(Context context) {
         this(context, null);
@@ -79,23 +106,24 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
+        setDim(getDim());
     }
 
     @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;
 
         // Bind the views
-        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
+        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
 
         if (mTaskDataLoaded) {
-            onTaskDataLoaded(false);
+            onTaskDataLoaded();
         }
     }
 
@@ -104,8 +132,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 +140,7 @@
         // Update the outline
         Outline o = new Outline();
         o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
-                config.taskViewShadowOutlineBottomInsetPx, radius);
+                mConfig.taskViewShadowOutlineBottomInsetPx, radius);
         setOutline(o);
     }
 
@@ -139,52 +166,64 @@
     }
 
     /** Synchronizes this view's properties with the task's transform */
-    void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
-                                             TaskViewTransform toTransform, int duration) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
+    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
+                    "duration: " + duration, Console.AnsiPurple);
+        }
 
         // Update the bar view
-        mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration);
+        mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
 
-        // Update this task view
+        // Check to see if any properties have changed, and update the task view
         if (duration > 0) {
-            if (animateFromTransform != null) {
-                setTranslationY(animateFromTransform.translationY);
-                if (Constants.DebugFlags.App.EnableShadows) {
-                    setTranslationZ(animateFromTransform.translationZ);
-                }
-                setScaleX(animateFromTransform.scale);
-                setScaleY(animateFromTransform.scale);
-                setAlpha(animateFromTransform.alpha);
+            ViewPropertyAnimator anim = animate();
+            boolean useLayers = false;
+
+            // Animate to the final state
+            if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
+                anim.translationY(toTransform.translationY);
             }
-            if (Constants.DebugFlags.App.EnableShadows) {
-                animate().translationZ(toTransform.translationZ);
+            if (Constants.DebugFlags.App.EnableShadows &&
+                    toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
+                anim.translationZ(toTransform.translationZ);
             }
-            animate().translationY(toTransform.translationY)
-                    .scaleX(toTransform.scale)
+            if (toTransform.hasScaleChangedFrom(getScaleX())) {
+                anim.scaleX(toTransform.scale)
                     .scaleY(toTransform.scale)
-                    .alpha(toTransform.alpha)
-                    .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
-                    .withLayer()
-                    .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                        @Override
-                        public void onAnimationUpdate(ValueAnimator animation) {
-                            updateDimOverlayFromScale();
-                        }
-                    })
-                    .start();
+                    .setUpdateListener(mUpdateDimListener);
+                useLayers = true;
+            }
+            if (toTransform.hasAlphaChangedFrom(getAlpha())) {
+                // Use layers if we animate alpha
+                anim.alpha(toTransform.alpha);
+                useLayers = true;
+            }
+            if (useLayers) {
+                anim.withLayer();
+            }
+            anim.setStartDelay(toTransform.startDelay)
+                .setDuration(duration)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .start();
         } else {
-            setTranslationY(toTransform.translationY);
-            if (Constants.DebugFlags.App.EnableShadows) {
+            // Set the changed properties
+            if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
+                setTranslationY(toTransform.translationY);
+            }
+            if (Constants.DebugFlags.App.EnableShadows &&
+                    toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
                 setTranslationZ(toTransform.translationZ);
             }
-            setScaleX(toTransform.scale);
-            setScaleY(toTransform.scale);
-            setAlpha(toTransform.alpha);
+            if (toTransform.hasScaleChangedFrom(getScaleX())) {
+                setScaleX(toTransform.scale);
+                setScaleY(toTransform.scale);
+                updateDimOverlayFromScale();
+            }
+            if (toTransform.hasAlphaChangedFrom(getAlpha())) {
+                setAlpha(toTransform.alpha);
+            }
         }
-        updateDimOverlayFromScale();
-        invalidate();
     }
 
     /** Resets this view's properties */
@@ -197,6 +236,7 @@
         setScaleX(1f);
         setScaleY(1f);
         setAlpha(1f);
+        setDim(0);
         invalidate();
     }
 
@@ -208,6 +248,7 @@
         // Fade the view out and slide it away
         toTransform.alpha = 0f;
         toTransform.translationY += 200;
+        toTransform.translationZ = 0;
     }
 
     /**
@@ -221,64 +262,174 @@
 
     /** 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 prepareEnterRecentsAnimation(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.prepareEnterRecentsAnimation();
+            } 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.prepareEnterRecentsAnimation();
+                // Set the dim to 0 so we can animate it in
+                setDim(0);
+            }
+
+        } else if (mConfig.launchedFromHome) {
+            // Move the task view off screen (below) so we can animate it in
+            setTranslationY(offscreenY);
+            setTranslationZ(0);
+            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 startEnterRecentsAnimation(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.startEnterRecentsAnimation(0, mEnableThumbnailClip);
+                        setVisibility(View.VISIBLE);
+                    }
+                });
+            } else {
+                // Animate the tasks down behind the full screenshot
+                animate()
+                        .scaleX(transform.scale)
+                        .scaleY(transform.scale)
+                        .translationY(transform.translationY)
+                        .setStartDelay(0)
+                        .setUpdateListener(null)
+                        .setInterpolator(mConfig.linearOutSlowInInterpolator)
+                        .setDuration(475)
+                        .withLayer()
+                        .withEndAction(mEnableThumbnailClip)
+                        .start();
+            }
+
+        } else if (mConfig.launchedFromAppWithThumbnail) {
+            if (ctx.isFrontMost) {
+                // Animate the task bar of the first task view
+                mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
+
+                // Animate the dim into view as well
+                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
+                anim.setStartDelay(mConfig.taskBarEnterAnimDelay);
+                anim.setDuration(mConfig.taskBarEnterAnimDuration);
+                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
+                anim.start();
+            } else {
+                mEnableThumbnailClip.run();
+            }
+
+        } 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)
+                    .translationZ(transform.translationZ)
+                    .setStartDelay(delay)
+                    .setUpdateListener(null)
+                    .setInterpolator(mConfig.quintOutInterpolator)
+                    .setDuration(mConfig.taskViewEnterFromHomeDuration)
+                    .withLayer()
+                    .withEndAction(mEnableThumbnailClip)
+                    .start();
+        }
+    }
+
+    /** Animates this task view as it leaves recents by pressing home. */
+    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        animate()
+                .translationY(ctx.offscreenTranslationY)
+                .setStartDelay(0)
+                .setUpdateListener(null)
+                .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                .setDuration(mConfig.taskViewExitToHomeDuration)
                 .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())
+    public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
+        if (isLaunchingTask) {
+            // Disable the thumbnail clip and animate the bar out
+            mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r);
+
+            // Animate the dim
+            if (mDim > 0) {
+                ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
+                anim.setDuration(mConfig.taskBarExitAnimDuration);
+                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
+                anim.start();
+            }
+        } else {
+            // Hide the dismiss button
+            mBarView.startLaunchTaskDismissAnimation();
+        }
+    }
+
+    /** Animates the deletion of this task view */
+    public void startDeleteTaskAnimation(final Runnable r) {
+        // Disabling clipping with the stack while the view is animating away
+        setClipViewInStack(false);
+
+        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
+            .alpha(0f)
             .setStartDelay(0)
-            .setInterpolator(config.fastOutLinearInInterpolator)
-            .setDuration(config.taskBarExitAnimDuration)
+            .setUpdateListener(null)
+            .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(true);
                 }
             })
             .start();
     }
 
-    /** Animates the deletion of this task view */
-    public void animateRemoval(final Runnable r) {
-        // Disabling clipping with the stack while the view is animating away
-        setClipViewInStack(false);
+    /** Animates this task view if the user does not interact with the stack after a certain time. */
+    public void startNoUserInteractionAnimation() {
+        mBarView.startNoUserInteractionAnimation();
+    }
 
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        animate().translationX(config.taskViewRemoveAnimTranslationXPx)
-            .alpha(0f)
-            .setStartDelay(0)
-            .setInterpolator(config.fastOutSlowInInterpolator)
-            .setDuration(config.taskViewRemoveAnimDuration)
-            .withLayer()
-            .withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    post(r);
-
-                    // Re-enable clipping with the stack (we will reuse this view)
-                    setClipViewInStack(false);
-                }
-            })
-            .start();
+    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
+    public void setNoUserInteractionState() {
+        mBarView.setNoUserInteractionState();
     }
 
     /** Returns the rect we want to clip (it may not be the full rect) */
@@ -292,12 +443,14 @@
 
     /** Enable the hw layers on this task view */
     void enableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
+        mBarView.enableHwLayers();
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
-        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
+        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
+        mBarView.disableHwLayers();
     }
 
     /**
@@ -305,7 +458,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        return mClipViewInStack;
+        return mClipViewInStack && (getVisibility() == View.VISIBLE);
     }
 
     /** Sets whether this view should be clipped, or clipped against. */
@@ -313,28 +466,44 @@
         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);
             }
         }
     }
 
-    /** Update the dim as a function of the scale of this view. */
-    void updateDimOverlayFromScale() {
-        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
+    /** Returns the current dim. */
+    public void setDim(int dim) {
+        mDim = dim;
+        postInvalidateOnAnimation();
+    }
+
+    /** Returns the current dim. */
+    public int getDim() {
+        return mDim;
+    }
+
+    /** Compute the dim as a function of the scale of this view. */
+    int getDimOverlayFromScale() {
+        float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
         float scaleRange = 1f - minScale;
         float dim = (1f - getScaleX()) / scaleRange;
         dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
-        mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
+        return Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
+    }
+
+    /** Update the dim as a function of the scale of this view. */
+    void updateDimOverlayFromScale() {
+        setDim(getDimOverlayFromScale());
     }
 
     @Override
     public void draw(Canvas canvas) {
+        int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
         // Apply the rounded rect clip path on the whole view
         canvas.clipPath(mRoundedRectClipPath);
-
         super.draw(canvas);
+        canvas.restoreToCount(restoreCount);
 
         // Apply the dim if necessary
         if (mDim > 0) {
@@ -382,17 +551,16 @@
     }
 
     @Override
-    public void onTaskDataLoaded(boolean reloadingTaskData) {
+    public void onTaskDataLoaded() {
         if (mThumbnailView != null && mBarView != null) {
             // Bind each of the views to the new task data
-            mThumbnailView.rebindToTask(mTask, reloadingTaskData);
-            mBarView.rebindToTask(mTask, reloadingTaskData);
+            mThumbnailView.rebindToTask(mTask);
+            mBarView.rebindToTask(mTask);
             // Rebind any listeners
             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);
                 }
             }
@@ -418,19 +586,25 @@
     }
 
     @Override
-    public void onClick(View v) {
-        if (v == mBarView.mApplicationIcon) {
-            mCb.onTaskIconClicked(this);
-        } else if (v == mBarView.mDismissButton) {
-            // Animate out the view and call the callback
-            final TaskView tv = this;
-            animateRemoval(new Runnable() {
-                @Override
-                public void run() {
-                    mCb.onTaskDismissed(tv);
+    public void onClick(final View v) {
+        // We purposely post the handler delayed to allow for the touch feedback to draw
+        final TaskView tv = this;
+        postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (v == mBarView.mApplicationIcon) {
+                    mCb.onTaskIconClicked(tv);
+                } else if (v == mBarView.mDismissButton) {
+                    // Animate out the view and call the callback
+                    startDeleteTaskAnimation(new Runnable() {
+                        @Override
+                        public void run() {
+                            mCb.onTaskDismissed(tv);
+                        }
+                    });
                 }
-            });
-        }
+            }
+        }, 125);
     }
 
     @Override
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..b351b03 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,6 +21,7 @@
 
 /* The transform state for a task view */
 public class TaskViewTransform {
+    public int startDelay = 0;
     public int translationY = 0;
     public int translationZ = 0;
     public float scale = 1f;
@@ -28,13 +29,14 @@
     public float dismissAlpha = 1f;
     public boolean visible = false;
     public Rect rect = new Rect();
-    float t;
+    float t = 0f;
 
     public TaskViewTransform() {
         // Do nothing
     }
 
     public TaskViewTransform(TaskViewTransform o) {
+        startDelay = o.startDelay;
         translationY = o.translationY;
         translationZ = o.translationZ;
         scale = o.scale;
@@ -45,9 +47,40 @@
         t = o.t;
     }
 
+    /** Resets the current transform */
+    public void reset() {
+        startDelay = 0;
+        translationY = 0;
+        translationZ = 0;
+        scale = 1f;
+        alpha = 1f;
+        dismissAlpha = 1f;
+        visible = false;
+        rect.setEmpty();
+        t = 0f;
+    }
+
+    /** Convenience functions to compare against current property values */
+    public boolean hasAlphaChangedFrom(float v) {
+        return (Float.compare(alpha, v) != 0);
+    }
+    public boolean hasDismissAlphaChangedFrom(float v) {
+        return (Float.compare(dismissAlpha, v) != 0);
+    }
+    public boolean hasScaleChangedFrom(float v) {
+        return (Float.compare(scale, v) != 0);
+    }
+    public boolean hasTranslationYChangedFrom(float v) {
+        return (Float.compare(translationY, v) != 0);
+    }
+    public boolean hasTranslationZChangedFrom(float v) {
+        return (Float.compare(translationZ, v) != 0);
+    }
+
     @Override
     public String toString() {
-        return "TaskViewTransform y: " + translationY + " scale: " + scale + " alpha: " + alpha +
-                " visible: " + visible + " rect: " + rect + " dismissAlpha: " + dismissAlpha;
+        return "TaskViewTransform delay: " + startDelay + " 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..13407aa
--- /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
+        FullscreenTransitionOverlayView 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(FullscreenTransitionOverlayView 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/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 8d19f50..1550217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -25,6 +25,8 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -89,6 +91,8 @@
             = new PathInterpolator(0, 0, 0.5f, 1);
 
     private boolean mDimmed;
+    private boolean mDark;
+    private final Paint mDarkPaint = createDarkPaint();
 
     private int mBgResId = com.android.internal.R.drawable.notification_material_bg;
     private int mDimmedBgResId = com.android.internal.R.drawable.notification_material_bg_dim;
@@ -119,6 +123,7 @@
 
     private NotificationBackgroundView mBackgroundNormal;
     private NotificationBackgroundView mBackgroundDimmed;
+    private NotificationScrimView mScrimView;
     private ObjectAnimator mBackgroundAnimator;
     private RectF mAppearAnimationRect = new RectF();
     private PorterDuffColorFilter mAppearAnimationFilter;
@@ -153,6 +158,7 @@
         mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
         updateBackground();
         updateBackgroundResources();
+        mScrimView = (NotificationScrimView) findViewById(R.id.scrim_view);
     }
 
     private final Runnable mTapTimeoutRunnable = new Runnable() {
@@ -293,6 +299,34 @@
         }
     }
 
+    public void setDark(boolean dark, boolean fade) {
+        // TODO implement fade
+        if (mDark != dark) {
+            mDark = dark;
+            if (mDark) {
+                setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+            } else {
+                setLayerType(View.LAYER_TYPE_NONE, null);
+            }
+        }
+    }
+
+    private static Paint createDarkPaint() {
+        final Paint p = new Paint();
+        final float[] invert = {
+            -1f,  0f,  0f, 1f, 1f,
+             0f, -1f,  0f, 1f, 1f,
+             0f,  0f, -1f, 1f, 1f,
+             0f,  0f,  0f, 1f, 0f
+        };
+        final ColorMatrix m = new ColorMatrix(invert);
+        final ColorMatrix grayscale = new ColorMatrix();
+        grayscale.setSaturation(0);
+        m.preConcat(grayscale);
+        p.setColorFilter(new ColorMatrixColorFilter(m));
+        return p;
+    }
+
     /**
      * Sets the resource id for the background of this notification.
      *
@@ -379,6 +413,7 @@
         setPivotY(actualHeight / 2);
         mBackgroundNormal.setActualHeight(actualHeight);
         mBackgroundDimmed.setActualHeight(actualHeight);
+        mScrimView.setActualHeight(actualHeight);
     }
 
     @Override
@@ -386,6 +421,7 @@
         super.setClipTopAmount(clipTopAmount);
         mBackgroundNormal.setClipTopAmount(clipTopAmount);
         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
+        mScrimView.setClipTopAmount(clipTopAmount);
     }
 
     @Override
@@ -405,6 +441,11 @@
         }
     }
 
+    @Override
+    public void setScrimAmount(float scrimAmount) {
+        mScrimView.setAlpha(scrimAmount);
+    }
+
     private void startAppearAnimation(boolean isAppearing,
             float translationDirection, long delay, final Runnable onFinishedRunnable) {
         if (mAppearAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index b94493e..c621f31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -89,11 +89,11 @@
 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;
-    private static final boolean USE_NOTIFICATION_LISTENER = true;
 
     protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -297,7 +297,7 @@
                 @Override
                 public void run() {
                     for (StatusBarNotification sbn : notifications) {
-                        addNotificationInternal(sbn, currentRanking);
+                        addNotification(sbn, currentRanking);
                     }
                 }
             });
@@ -324,16 +324,16 @@
                         // wasn't a group child, remove the old instance.
                         // Otherwise just update the ranking.
                         if (isUpdate) {
-                            removeNotificationInternal(sbn.getKey(), rankingMap);
+                            removeNotification(sbn.getKey(), rankingMap);
                         } else {
-                            updateRankingInternal(rankingMap);
+                            updateNotificationRanking(rankingMap);
                         }
                         return;
                     }
                     if (isUpdate) {
-                        updateNotificationInternal(sbn, rankingMap);
+                        updateNotification(sbn, rankingMap);
                     } else {
-                        addNotificationInternal(sbn, rankingMap);
+                        addNotification(sbn, rankingMap);
                     }
                 }
             });
@@ -346,7 +346,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    removeNotificationInternal(sbn.getKey(), rankingMap);
+                    removeNotification(sbn.getKey(), rankingMap);
                 }
             });
         }
@@ -357,7 +357,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    updateRankingInternal(rankingMap);
+                    updateNotificationRanking(rankingMap);
                 }
             });
         }
@@ -404,6 +404,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mRecents = getComponent(RecentsComponent.class);
+        mRecents.setCallback(this);
 
         mLocale = mContext.getResources().getConfiguration().locale;
         mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
@@ -412,14 +413,12 @@
 
         // Connect in to the status bar manager service
         StatusBarIconList iconList = new StatusBarIconList();
-        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
         mCommandQueue = new CommandQueue(this, iconList);
 
         int[] switches = new int[8];
         ArrayList<IBinder> binders = new ArrayList<IBinder>();
         try {
-            mBarService.registerStatusBar(mCommandQueue, iconList, notifications,
-                    switches, binders);
+            mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders);
         } catch (RemoteException ex) {
             // If the system process isn't there we're doomed anyway.
         }
@@ -445,19 +444,12 @@
         }
 
         // Set up the initial notification state.
-        if (USE_NOTIFICATION_LISTENER) {
-            try {
-                mNotificationListener.registerAsSystemService(
-                        new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
-                        UserHandle.USER_ALL);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to register notification listener", e);
-            }
-        } else {
-            N = notifications.size();
-            for (int i=0; i<N; i++) {
-                addNotification(notifications.get(i));
-            }
+        try {
+            mNotificationListener.registerAsSystemService(
+                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+                    UserHandle.USER_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to register notification listener", e);
         }
 
 
@@ -787,6 +779,11 @@
         }
     }
 
+    @Override
+    public void onVisibilityChanged(boolean visible) {
+        // Do nothing
+    }
+
     public abstract void resetHeadsUpDecayTimer();
 
     public abstract void scheduleHeadsUpOpen();
@@ -1187,7 +1184,7 @@
      * WARNING: this will call back into us.  Don't hold any locks.
      */
     void handleNotificationError(StatusBarNotification n, String message) {
-        removeNotification(n.getKey());
+        removeNotification(n.getKey(), null);
         try {
             mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
                     n.getInitialPid(), message, n.getUserId());
@@ -1323,34 +1320,11 @@
     protected abstract void updateExpandedViewPos(int expandedPosition);
     protected abstract boolean shouldDisableNavbarGestures();
 
-    @Override
-    public void addNotification(StatusBarNotification notification) {
-        if (!USE_NOTIFICATION_LISTENER) {
-            addNotificationInternal(notification, null);
-        }
-    }
-
-    public abstract void addNotificationInternal(StatusBarNotification notification,
+    public abstract void addNotification(StatusBarNotification notification,
             RankingMap ranking);
-
-    protected abstract void updateRankingInternal(RankingMap ranking);
-
-    @Override
-    public void removeNotification(String key) {
-        if (!USE_NOTIFICATION_LISTENER) {
-            removeNotificationInternal(key, null);
-        }
-    }
-
-    public abstract void removeNotificationInternal(String key, RankingMap ranking);
-
-    public void updateNotification(StatusBarNotification notification) {
-        if (!USE_NOTIFICATION_LISTENER) {
-            updateNotificationInternal(notification, null);
-        }
-    }
-
-    public void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+    protected abstract void updateNotificationRanking(RankingMap ranking);
+    public abstract void removeNotification(String key, RankingMap ranking);
+    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
         if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
 
         final String key = notification.getKey();
@@ -1467,7 +1441,7 @@
                 } else {
                     if (shouldInterrupt && alertAgain) {
                         removeNotificationViews(key, ranking);
-                        addNotificationInternal(notification, ranking);  //this will pop the headsup
+                        addNotification(notification, ranking);  //this will pop the headsup
                     } else {
                         updateNotificationViews(oldEntry, notification);
                     }
@@ -1507,7 +1481,7 @@
                 if (shouldInterrupt && alertAgain) {
                     if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
                     removeNotificationViews(key, ranking);
-                    addNotificationInternal(notification, ranking);  //this will pop the headsup
+                    addNotification(notification, ranking);  //this will pop the headsup
                 } else {
                     if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
                     removeNotificationViews(key, ranking);
@@ -1665,12 +1639,10 @@
             mWindowManager.removeViewImmediate(mSearchPanelView);
         }
         mContext.unregisterReceiver(mBroadcastReceiver);
-        if (USE_NOTIFICATION_LISTENER) {
-            try {
-                mNotificationListener.unregisterAsSystemService();
-            } catch (RemoteException e) {
-                // Ignore.
-            }
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            // Ignore.
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index aaeadb6..2b61f09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -41,23 +41,20 @@
     private static final int OP_REMOVE_ICON = 2;
 
     private static final int MSG_ICON                       = 1 << MSG_SHIFT;
-    private static final int MSG_ADD_NOTIFICATION           = 2 << MSG_SHIFT;
-    private static final int MSG_UPDATE_NOTIFICATION        = 3 << MSG_SHIFT;
-    private static final int MSG_REMOVE_NOTIFICATION        = 4 << MSG_SHIFT;
-    private static final int MSG_DISABLE                    = 5 << MSG_SHIFT;
-    private static final int MSG_EXPAND_NOTIFICATIONS       = 6 << MSG_SHIFT;
-    private static final int MSG_COLLAPSE_PANELS            = 7 << MSG_SHIFT;
-    private static final int MSG_EXPAND_SETTINGS            = 8 << MSG_SHIFT;
-    private static final int MSG_SET_SYSTEMUI_VISIBILITY    = 9 << MSG_SHIFT;
-    private static final int MSG_TOP_APP_WINDOW_CHANGED     = 10 << MSG_SHIFT;
-    private static final int MSG_SHOW_IME_BUTTON            = 11 << MSG_SHIFT;
-    private static final int MSG_SET_HARD_KEYBOARD_STATUS   = 12 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_RECENT_APPS         = 13 << MSG_SHIFT;
-    private static final int MSG_PRELOAD_RECENT_APPS        = 14 << MSG_SHIFT;
-    private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT;
-    private static final int MSG_SET_WINDOW_STATE           = 16 << MSG_SHIFT;
-    private static final int MSG_SHOW_RECENT_APPS           = 17 << MSG_SHIFT;
-    private static final int MSG_HIDE_RECENT_APPS           = 18 << MSG_SHIFT;
+    private static final int MSG_DISABLE                    = 2 << MSG_SHIFT;
+    private static final int MSG_EXPAND_NOTIFICATIONS       = 3 << MSG_SHIFT;
+    private static final int MSG_COLLAPSE_PANELS            = 4 << MSG_SHIFT;
+    private static final int MSG_EXPAND_SETTINGS            = 5 << MSG_SHIFT;
+    private static final int MSG_SET_SYSTEMUI_VISIBILITY    = 6 << MSG_SHIFT;
+    private static final int MSG_TOP_APP_WINDOW_CHANGED     = 7 << MSG_SHIFT;
+    private static final int MSG_SHOW_IME_BUTTON            = 8 << MSG_SHIFT;
+    private static final int MSG_SET_HARD_KEYBOARD_STATUS   = 9 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_RECENT_APPS         = 10 << MSG_SHIFT;
+    private static final int MSG_PRELOAD_RECENT_APPS        = 11 << MSG_SHIFT;
+    private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 12 << MSG_SHIFT;
+    private static final int MSG_SET_WINDOW_STATE           = 13 << MSG_SHIFT;
+    private static final int MSG_SHOW_RECENT_APPS           = 14 << MSG_SHIFT;
+    private static final int MSG_HIDE_RECENT_APPS           = 15 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -80,9 +77,6 @@
         public void updateIcon(String slot, int index, int viewIndex,
                 StatusBarIcon old, StatusBarIcon icon);
         public void removeIcon(String slot, int index, int viewIndex);
-        public void addNotification(StatusBarNotification notification);
-        public void updateNotification(StatusBarNotification notification);
-        public void removeNotification(String key);
         public void disable(int state);
         public void animateExpandNotificationsPanel();
         public void animateCollapsePanels(int flags);
@@ -123,26 +117,6 @@
         }
     }
 
-    @Override
-    public void addNotification(StatusBarNotification notification) {
-        synchronized (mList) {
-            mHandler.obtainMessage(MSG_ADD_NOTIFICATION, 0, 0, notification).sendToTarget();
-        }
-    }
-
-    @Override
-    public void updateNotification(StatusBarNotification notification) {
-        synchronized (mList) {
-            mHandler.obtainMessage(MSG_UPDATE_NOTIFICATION, 0, 0, notification).sendToTarget();
-        }
-    }
-
-    public void removeNotification(String key) {
-        synchronized (mList) {
-            mHandler.obtainMessage(MSG_REMOVE_NOTIFICATION, 0, 0, key).sendToTarget();
-        }
-    }
-
     public void disable(int state) {
         synchronized (mList) {
             mHandler.removeMessages(MSG_DISABLE);
@@ -279,18 +253,6 @@
                     }
                     break;
                 }
-                case MSG_ADD_NOTIFICATION: {
-                    mCallbacks.addNotification((StatusBarNotification) msg.obj);
-                    break;
-                }
-                case MSG_UPDATE_NOTIFICATION: {
-                    mCallbacks.updateNotification((StatusBarNotification) msg.obj);
-                    break;
-                }
-                case MSG_REMOVE_NOTIFICATION: {
-                    mCallbacks.removeNotification((String) msg.obj);
-                    break;
-                }
                 case MSG_DISABLE:
                     mCallbacks.disable(msg.arg1);
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5bad602..5981898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -212,13 +212,6 @@
         return mShowingPublic ? mRowMinHeight : mMaxExpandHeight;
     }
 
-    /**
-     * @return the potential height this view could expand in addition.
-     */
-    public int getExpandPotential() {
-        return getIntrinsicHeight() - getActualHeight();
-    }
-
     @Override
     public boolean isContentExpandable() {
         NotificationContentView showingLayout = getShowingLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index ac2537c..c3fb83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -167,6 +167,15 @@
     }
 
     /**
+     * Sets the notification as dark. The default implementation does nothing.
+     *
+     * @param dark Whether the notification should be dark.
+     * @param fade Whether an animation should be played to change the state.
+     */
+    public void setDark(boolean dark, boolean fade) {
+    }
+
+    /**
      * @return The desired notification height.
      */
     public int getIntrinsicHeight() {
@@ -223,6 +232,8 @@
 
     public abstract void performAddAnimation(long delay);
 
+    public abstract void setScrimAmount(float scrimAmount);
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
deleted file mode 100644
index bfa74fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
+++ /dev/null
@@ -1,143 +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.systemui.statusbar;
-
-import android.app.Notification;
-import android.content.Context;
-import android.os.Process;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-public class InterceptedNotifications {
-    private static final String TAG = "InterceptedNotifications";
-    private static final String SYNTHETIC_KEY = "InterceptedNotifications.SYNTHETIC_KEY";
-
-    private final Context mContext;
-    private final PhoneStatusBar mBar;
-    private final ArrayMap<String, StatusBarNotification> mIntercepted
-            = new ArrayMap<String, StatusBarNotification>();
-    private final ArraySet<String> mReleased = new ArraySet<String>();
-
-    private String mSynKey;
-
-    public InterceptedNotifications(Context context, PhoneStatusBar bar) {
-        mContext = context;
-        mBar = bar;
-    }
-
-    public void releaseIntercepted() {
-        final int n = mIntercepted.size();
-        for (int i = 0; i < n; i++) {
-            final StatusBarNotification sbn = mIntercepted.valueAt(i);
-            mReleased.add(sbn.getKey());
-            mBar.displayNotification(sbn, null);
-        }
-        mIntercepted.clear();
-        updateSyntheticNotification();
-    }
-
-    public boolean tryIntercept(StatusBarNotification notification, RankingMap rankingMap) {
-        if (rankingMap == null) return false;
-        if (shouldDisplayIntercepted()) return false;
-        if (mReleased.contains(notification.getKey())) return false;
-        Ranking ranking = rankingMap.getRanking(notification.getKey());
-        if (!ranking.isInterceptedByDoNotDisturb()) return false;
-        mIntercepted.put(notification.getKey(), notification);
-        updateSyntheticNotification();
-        return true;
-    }
-
-    public void retryIntercepts(RankingMap ranking) {
-        if (ranking == null) return;
-
-        final int N = mIntercepted.size();
-        final ArraySet<String> removed = new ArraySet<String>(N);
-        for (int i = 0; i < N; i++) {
-            final StatusBarNotification sbn = mIntercepted.valueAt(i);
-            if (!tryIntercept(sbn, ranking)) {
-                removed.add(sbn.getKey());
-                mBar.displayNotification(sbn, ranking);
-            }
-        }
-        if (!removed.isEmpty()) {
-            mIntercepted.removeAll(removed);
-            updateSyntheticNotification();
-        }
-    }
-
-    public void remove(String key) {
-        if (mIntercepted.remove(key) != null) {
-            updateSyntheticNotification();
-        }
-        mReleased.remove(key);
-    }
-
-    public boolean isSyntheticEntry(Entry ent) {
-        return ent.key.equals(mSynKey);
-    }
-
-    private boolean shouldDisplayIntercepted() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.DISPLAY_INTERCEPTED_NOTIFICATIONS, 0) != 0;
-    }
-
-    private void updateSyntheticNotification() {
-        if (mIntercepted.isEmpty()) {
-            if (mSynKey != null) {
-                mBar.removeNotificationInternal(mSynKey, null);
-                mSynKey = null;
-            }
-            return;
-        }
-        final Notification n = new Notification.Builder(mContext)
-                .setSmallIcon(R.drawable.ic_notify_zen)
-                .setContentTitle(mContext.getResources().getQuantityString(
-                        R.plurals.zen_mode_notification_title,
-                        mIntercepted.size(), mIntercepted.size()))
-                .setContentText(mContext.getString(R.string.zen_mode_notification_text))
-                .setOngoing(true)
-                .build();
-        final StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
-                mContext.getBasePackageName(),
-                TAG.hashCode(), TAG, Process.myUid(), Process.myPid(), 0, n,
-                mBar.getCurrentUserHandle());
-        if (mSynKey == null) {
-            mSynKey = sbn.getKey();
-            mBar.displayNotification(sbn, null);
-        } else {
-           mBar.updateNotificationInternal(sbn, null);
-        }
-        final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey);
-        entry.row.setOnClickListener(mSynClickListener);
-    }
-
-    private final View.OnClickListener mSynClickListener = new View.OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            releaseIntercepted();
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 5cde979..f919501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -17,12 +17,16 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.view.Gravity;
 import android.view.View;
-
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
+
 import com.android.systemui.R;
 
 /**
@@ -32,6 +36,8 @@
  */
 public class NotificationContentView extends FrameLayout {
 
+    private static final long ANIMATION_DURATION_LENGTH = 170;
+
     private final Rect mClipBounds = new Rect();
 
     private View mContractedChild;
@@ -41,10 +47,17 @@
     private int mClipTopAmount;
     private int mActualHeight;
 
+    private final Interpolator mLinearInterpolator = new LinearInterpolator();
+
+    private boolean mContractedVisible = true;
+
+    private Paint mFadePaint = new Paint();
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         mActualHeight = mSmallHeight;
+        mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
     }
 
     @Override
@@ -60,7 +73,7 @@
         sanitizeContractedLayoutParams(child);
         addView(child);
         mContractedChild = child;
-        selectLayout();
+        selectLayout(false /* animate */, true /* force */);
     }
 
     public void setExpandedChild(View child) {
@@ -69,12 +82,12 @@
         }
         addView(child);
         mExpandedChild = child;
-        selectLayout();
+        selectLayout(false /* animate */, true /* force */);
     }
 
     public void setActualHeight(int actualHeight) {
         mActualHeight = actualHeight;
-        selectLayout();
+        selectLayout(true /* animate */, false /* force */);
         updateClipping();
     }
 
@@ -104,26 +117,60 @@
         contractedChild.setLayoutParams(lp);
     }
 
-    private void selectLayout() {
-        if (mActualHeight <= mSmallHeight || mExpandedChild == null) {
-            if (mContractedChild != null && mContractedChild.getVisibility() != View.VISIBLE) {
-                mContractedChild.setVisibility(View.VISIBLE);
-            }
-            if (mExpandedChild != null && mExpandedChild.getVisibility() != View.INVISIBLE) {
-                mExpandedChild.setVisibility(View.INVISIBLE);
-            }
-        } else {
-            if (mExpandedChild.getVisibility() != View.VISIBLE) {
-                mExpandedChild.setVisibility(View.VISIBLE);
-            }
-            if (mContractedChild != null && mContractedChild.getVisibility() != View.INVISIBLE) {
-                mContractedChild.setVisibility(View.INVISIBLE);
+    private void selectLayout(boolean animate, boolean force) {
+        if (mContractedChild == null) {
+            return;
+        }
+        boolean showContractedChild = showContractedChild();
+        if (showContractedChild != mContractedVisible || force) {
+            if (animate && mExpandedChild != null) {
+                runSwitchAnimation(showContractedChild);
+            } else if (mExpandedChild != null) {
+                mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
+                mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
+                mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
+                mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
             }
         }
+        mContractedVisible = showContractedChild;
+    }
+
+    private void runSwitchAnimation(final boolean showContractedChild) {
+        mContractedChild.setVisibility(View.VISIBLE);
+        mExpandedChild.setVisibility(View.VISIBLE);
+        mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+        mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+        mContractedChild.animate()
+                .alpha(showContractedChild ? 1f : 0f)
+                .setDuration(ANIMATION_DURATION_LENGTH)
+                .setInterpolator(mLinearInterpolator);
+        mExpandedChild.animate()
+                .alpha(showContractedChild ? 0f : 1f)
+                .setDuration(ANIMATION_DURATION_LENGTH)
+                .setInterpolator(mLinearInterpolator)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+                        mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
+                        setLayerType(LAYER_TYPE_NONE, null);
+                        mContractedChild.setVisibility(showContractedChild
+                                ? View.VISIBLE
+                                : View.INVISIBLE);
+                        mExpandedChild.setVisibility(showContractedChild
+                                ? View.INVISIBLE
+                                : View.VISIBLE);
+                    }
+                });
+    }
+
+    private boolean showContractedChild() {
+        return mActualHeight <= mSmallHeight || mExpandedChild == null;
     }
 
     public void notifyContentUpdated() {
-        selectLayout();
+        selectLayout(false /* animate */, true /* force */);
     }
 
     public boolean isContentExpandable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
new file mode 100644
index 0000000..440b2c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationScrimView.java
@@ -0,0 +1,79 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.keyguard.R;
+
+/**
+ * A view that can be used for both the dimmed and normal background of an notification.
+ */
+public class NotificationScrimView extends View {
+
+    private Drawable mBackground;
+    private int mClipTopAmount;
+    private int mActualHeight;
+
+    public NotificationScrimView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mBackground = getResources().getDrawable(R.drawable.notification_scrim);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        draw(canvas, mBackground);
+    }
+
+    private void draw(Canvas canvas, Drawable drawable) {
+        if (drawable != null) {
+            drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+            drawable.draw(canvas);
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mBackground;
+    }
+
+    public void setActualHeight(int actualHeight) {
+        mActualHeight = actualHeight;
+        invalidate();
+    }
+
+    public int getActualHeight() {
+        return mActualHeight;
+    }
+
+    public void setClipTopAmount(int clipTopAmount) {
+        mClipTopAmount = clipTopAmount;
+        invalidate();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+
+        // Prevents this view from creating a layer when alpha is animating.
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
index f80f0fd..650abaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -121,4 +121,9 @@
     public void performAddAnimation(long delay) {
         performVisibilityAnimation(true);
     }
+
+    @Override
+    public void setScrimAmount(float scrimAmount) {
+        // We don't need to scrim the speedbumps
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 97aa993..63698e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -58,6 +58,7 @@
     private ImageView mCameraImageView;
     private ImageView mPhoneImageView;
     private ImageView mLockIcon;
+    private View mIndicationText;
 
     private ActivityStarter mActivityStarter;
     private UnlockMethodCache mUnlockMethodCache;
@@ -87,6 +88,7 @@
         mCameraImageView = (ImageView) findViewById(R.id.camera_button);
         mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
         mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+        mIndicationText = findViewById(R.id.keyguard_indication_text);
         watchForCameraPolicyChanges();
         watchForAccessibilityChanges();
         updateCameraVisibility();
@@ -231,6 +233,10 @@
         return mLockIcon;
     }
 
+    public View getIndicationView() {
+        return mIndicationText;
+    }
+
     @Override
     public void onMethodSecureChanged(boolean methodSecure) {
         updateTrust();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 6a83a5e..319096d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.graphics.Path;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
@@ -31,6 +32,10 @@
 
     private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f;
     private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f;
+    private static final float CLOCK_SCALE_FADE_START = 0.95f;
+    private static final float CLOCK_SCALE_FADE_END = 0.75f;
+    private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f;
+
 
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
@@ -61,6 +66,8 @@
         sSlowDownInterpolator = new PathInterpolator(path);
     }
 
+    private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
+
     /**
      * Refreshes the dimension values.
      */
@@ -87,18 +94,29 @@
     }
 
     public void run(Result result) {
-        int y = getClockY() - mKeyguardStatusHeight/2;
+        int y = getClockY() - mKeyguardStatusHeight / 2;
         float clockAdjustment = getClockYExpansionAdjustment();
         float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier();
         result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier);
         int clockNotificationsPadding = getClockNotificationsPadding()
                 + result.stackScrollerPaddingAdjustment;
         int padding = y + clockNotificationsPadding;
-        y += clockAdjustment;
         result.clockY = y;
         result.stackScrollerPadding = mKeyguardStatusHeight + padding;
-        result.clockAlpha = getClockAlpha(result.stackScrollerPadding
-                - (y + mKeyguardStatusHeight));
+        result.clockScale = getClockScale(result.stackScrollerPadding,
+                result.clockY,
+                y + getClockNotificationsPadding() + mKeyguardStatusHeight);
+        result.clockAlpha = getClockAlpha(result.clockScale);
+    }
+
+    private float getClockScale(int notificationPadding, int clockY, int startPadding) {
+        float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f;
+        float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier;
+        float distanceToScaleEnd = notificationPadding - scaleEnd;
+        float progress = distanceToScaleEnd / (startPadding - scaleEnd);
+        progress = Math.max(0.0f, Math.min(progress, 1.0f));
+        progress = mAccelerateInterpolator.getInterpolation(progress);
+        return progress;
     }
 
     private int getClockNotificationsPadding() {
@@ -144,11 +162,12 @@
                 + t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX;
     }
 
-    private float getClockAlpha(int clockNotificationPadding) {
-        float t = getNotificationAmountT();
-        t = (float) Math.pow(t, 0.3f);
-        float multiplier = 1 + 2 * (1 - t);
-        float alpha = 1 + (float) clockNotificationPadding * multiplier / mKeyguardStatusHeight * 3;
+    private float getClockAlpha(float scale) {
+        float fadeEnd = getNotificationAmountT() == 0.0f
+                ? CLOCK_SCALE_FADE_END_NO_NOTIFS
+                : CLOCK_SCALE_FADE_END;
+        float alpha = (scale - fadeEnd)
+                / (CLOCK_SCALE_FADE_START - fadeEnd);
         return Math.max(0, Math.min(1, alpha));
     }
 
@@ -168,6 +187,11 @@
         public int clockY;
 
         /**
+         * The scale of the Clock
+         */
+        public float clockScale;
+
+        /**
          * The alpha value of the clock.
          */
         public float clockAlpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index dde95bf..cec7592 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
@@ -48,6 +49,11 @@
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardPageSwipeHelper.Callback {
 
+    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+    // changed.
+    private static final int CAP_HEIGHT = 1456;
+    private static final int FONT_HEIGHT = 2163;
+
     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
 
     private KeyguardPageSwipeHelper mPageSwiper;
@@ -56,7 +62,7 @@
     private View mQsPanel;
     private View mKeyguardStatusView;
     private ObservableScrollView mScrollView;
-    private View mStackScrollerContainer;
+    private TextView mClockView;
 
     private NotificationStackScrollLayout mNotificationStackScroller;
     private int mNotificationTopPadding;
@@ -103,29 +109,19 @@
             new KeyguardClockPositionAlgorithm();
     private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
             new KeyguardClockPositionAlgorithm.Result();
-    private boolean mIsSwipedHorizontally;
     private boolean mIsExpanding;
-    private KeyguardBottomAreaView mKeyguardBottomArea;
+
     private boolean mBlockTouches;
     private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
+    private int mNotificationScrimWaitDistance;
+    private boolean mOnNotificationsOnDown;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     public void setStatusBar(PhoneStatusBar bar) {
-        if (mStatusBar != null) {
-            mStatusBar.setOnFlipRunnable(null);
-        }
         mStatusBar = bar;
-        if (bar != null) {
-            mStatusBar.setOnFlipRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    requestPanelHeightUpdate();
-                }
-            });
-        }
     }
 
     @Override
@@ -135,9 +131,9 @@
         mHeader.getBackgroundView().setOnClickListener(this);
         mHeader.setOverlayParent(this);
         mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
-        mStackScrollerContainer = findViewById(R.id.notification_container_parent);
         mQsContainer = findViewById(R.id.quick_settings_container);
         mQsPanel = findViewById(R.id.quick_settings_panel);
+        mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
         mScrollView.setListener(this);
         mNotificationStackScroller = (NotificationStackScrollLayout)
@@ -169,12 +165,19 @@
                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
         mClockPositionAlgorithm.loadDimens(getResources());
+        mNotificationScrimWaitDistance =
+                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
+        // Update Clock Pivot
+        mKeyguardStatusView.setPivotX(getWidth() / 2);
+        mKeyguardStatusView.setPivotY(
+                (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
+
         // Calculate quick setting heights.
         mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
         mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
@@ -197,7 +200,7 @@
      * showing.
      */
     private void positionClockAndNotifications() {
-        boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
             int bottom = mStackScrollerOverscrolling
@@ -215,17 +218,17 @@
                     getHeight(),
                     mKeyguardStatusView.getHeight());
             mClockPositionAlgorithm.run(mClockPositionResult);
-            if (animateClock || mClockAnimator != null) {
+            if (animate || mClockAnimator != null) {
                 startClockAnimation(mClockPositionResult.clockY);
             } else {
                 mKeyguardStatusView.setY(mClockPositionResult.clockY);
             }
-            applyClockAlpha(mClockPositionResult.clockAlpha);
+            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
             mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
         }
         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
-        requestScrollerTopPaddingUpdate(animateClock);
+        requestScrollerTopPaddingUpdate(animate);
     }
 
     private void startClockAnimation(int y) {
@@ -258,13 +261,10 @@
         });
     }
 
-    private void applyClockAlpha(float alpha) {
-        if (alpha != 1.0f) {
-            mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
-        } else {
-            mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
-        }
+    private void updateClock(float alpha, float scale) {
         mKeyguardStatusView.setAlpha(alpha);
+        mKeyguardStatusView.setScaleX(scale);
+        mKeyguardStatusView.setScaleY(scale);
     }
 
     public void animateToFullShade() {
@@ -290,6 +290,8 @@
         mUnlockIconActive = false;
         mPageSwiper.reset();
         closeQs();
+        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
+                true /* cancelAnimators */);
     }
 
     public void closeQs() {
@@ -344,6 +346,7 @@
                 mInitialTouchX = x;
                 initVelocityTracker();
                 trackMovement(event);
+                mOnNotificationsOnDown = isOnNotifications(x, y);
                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
                     getParent().requestDisallowInterceptTouchEvent(true);
                 }
@@ -391,6 +394,8 @@
                 if (mQsTracking) {
                     flingQsWithCurrentVelocity();
                     mQsTracking = false;
+                } else if (mQsFullyExpanded && mOnNotificationsOnDown) {
+                    flingSettings(0 /* vel */, false /* expand */);
                 }
                 mIntercepting = false;
                 break;
@@ -398,6 +403,10 @@
         return !mQsExpanded && super.onInterceptTouchEvent(event);
     }
 
+    private boolean isOnNotifications(float x, float y) {
+        return mNotificationStackScroller.getChildAtPosition(x, y) != null;
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
 
@@ -577,9 +586,17 @@
         mHeader.setExpansion(height - mQsPeekHeight);
         setQsTranslation(height);
         requestScrollerTopPaddingUpdate(false /* animate */);
+        updateNotificationScrim(height);
         mStatusBar.userActivity();
     }
 
+    private void updateNotificationScrim(float height) {
+        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
+        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
+        progress = Math.max(0.0f, Math.min(progress, 1.0f));
+        mNotificationStackScroller.setScrimAlpha(progress);
+    }
+
     private void setQsTranslation(float height) {
         mQsContainer.setY(height - mQsContainer.getHeight());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index b94f6f3..b4faaaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -188,10 +188,10 @@
                 pv.collapse();
                 waiting = true;
             } else {
+                pv.resetViews();
                 pv.setExpandedFraction(0); // just in case
                 pv.setVisibility(View.GONE);
                 pv.cancelPeek();
-                pv.resetViews();
             }
         }
         if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 1f3098d..12aa004 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -62,6 +62,7 @@
     protected int mTouchSlop;
     protected boolean mHintAnimationRunning;
     private boolean mOverExpandedBeforeFling;
+    private float mOriginalIndicationY;
 
     private ValueAnimator mHeightAnimator;
     private ObjectAnimator mPeekAnimator;
@@ -82,6 +83,7 @@
 
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mBounceInterpolator;
+    protected KeyguardBottomAreaView mKeyguardBottomArea;
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
@@ -652,6 +654,22 @@
         });
         animator.start();
         mHeightAnimator = animator;
+        mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY();
+        mKeyguardBottomArea.getIndicationView().animate()
+                .y(mOriginalIndicationY - mHintDistance)
+                .setDuration(250)
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mKeyguardBottomArea.getIndicationView().animate()
+                                .y(mOriginalIndicationY)
+                                .setDuration(450)
+                                .setInterpolator(mBounceInterpolator)
+                                .start();
+                    }
+                })
+                .start();
     }
 
     /**
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 c11a9ac..62ac54e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -35,6 +35,7 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -63,7 +64,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
+import android.provider.Settings.SettingNotFoundException;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -82,7 +83,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewStub;
-import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
@@ -99,6 +99,7 @@
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.doze.DozeService;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.qs.CircularClipper;
 import com.android.systemui.qs.QSPanel;
@@ -109,7 +110,6 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.InterceptedNotifications;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
@@ -123,6 +123,7 @@
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
@@ -167,11 +168,6 @@
     private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
     private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
 
-    /**
-     * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
-     */
-    private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
-
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
     private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -184,21 +180,6 @@
      */
     private static final int HINT_RESET_DELAY_MS = 1200;
 
-    // fling gesture tuning parameters, scaled to display density
-    private float mSelfExpandVelocityPx; // classic value: 2000px/s
-    private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
-    private float mFlingExpandMinVelocityPx; // classic value: 200px/s
-    private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
-    private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
-    private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
-    private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
-
-    private float mExpandAccelPx; // classic value: 2000px/s/s
-    private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
-
-    private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
-                                                    // faster than mSelfCollapseVelocityPx)
-
     PhoneStatusBarPolicy mIconPolicy;
 
     // These are no longer handled by the policy, because we need custom strategies for them
@@ -211,6 +192,7 @@
     ZenModeController mZenModeController;
     CastControllerImpl mCastController;
     VolumeComponent mVolumeComponent;
+    KeyguardUserSwitcher mKeyguardUserSwitcher;
 
     int mNaturalBarHeight = -1;
     int mIconSize = -1;
@@ -223,6 +205,7 @@
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private StatusBarWindowManager mStatusBarWindowManager;
     private UnlockMethodCache mUnlockMethodCache;
+    private DozeServiceHost mDozeServiceHost;
 
     int mPixelFormat;
     Object mQueueLock = new Object();
@@ -260,6 +243,10 @@
     boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationController mKeyguardIndicationController;
 
+    private boolean mKeyguardFadingAway;
+    private long mKeyguardFadingAwayDelay;
+    private long mKeyguardFadingAwayDuration;
+
     int mKeyguardMaxNotificationCount;
     View mDateTimeView;
 
@@ -267,7 +254,6 @@
     private TextView mCarrierLabel;
     private boolean mCarrierLabelVisible = false;
     private int mCarrierLabelHeight;
-    private TextView mEmergencyCallLabel;
     private int mStatusBarHeaderHeight;
 
     private boolean mShowCarrierInPanel = false;
@@ -392,15 +378,11 @@
             }
         }};
 
-    private Runnable mOnFlipRunnable;
-    private InterceptedNotifications mIntercepted;
-    private VelocityTracker mSettingsTracker;
-    private float mSettingsDownY;
-    private boolean mSettingsStarted;
-    private boolean mSettingsCancelled;
-    private boolean mSettingsClosing;
     private boolean mVisible;
+    private boolean mWaitingForKeyguardExit;
+    private boolean mDozing;
 
+    private Interpolator mLinearOutSlowIn;
     private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
     private Interpolator mAlphaIn = new PathInterpolator(0f, 0f, 0.8f, 1f);
 
@@ -414,14 +396,12 @@
 
     private int mDisabledUnmodified;
 
-    public void setOnFlipRunnable(Runnable onFlipRunnable) {
-        mOnFlipRunnable = onFlipRunnable;
-    }
-
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>();
     private long mLastVisibilityReportUptimeMs;
 
+    private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
+
     private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
             | ViewState.LOCATION_TOP_STACK_PEEKING
             | ViewState.LOCATION_MAIN_AREA
@@ -502,19 +482,6 @@
     };
 
     @Override
-    public void setZenMode(int mode) {
-        super.setZenMode(mode);
-        if (!isDeviceProvisioned()) return;
-        final boolean zen = mode != Settings.Global.ZEN_MODE_OFF;
-        if (!zen) {
-            mIntercepted.releaseIntercepted();
-        }
-        if (mIconPolicy != null) {
-            mIconPolicy.setZenMode(zen);
-        }
-    }
-
-    @Override
     protected void setShowLockscreenNotifications(boolean show) {
         super.setShowLockscreenNotifications(show);
         updateStackScrollerState();
@@ -525,7 +492,6 @@
         mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                 .getDefaultDisplay();
         updateDisplaySize();
-        mIntercepted = new InterceptedNotifications(mContext, this);
         super.start(); // calls createAndAddWindows()
 
         addNavigationBar();
@@ -545,6 +511,9 @@
         }
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         startKeyguard();
+
+        mDozeServiceHost = new DozeServiceHost();
+        putComponent(DozeService.Host.class, mDozeServiceHost);
     }
 
     // ================================================================================
@@ -714,26 +683,14 @@
         final SignalClusterView signalCluster =
                 (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
 
+        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
+                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader);
 
         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 +716,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) {
@@ -906,6 +865,14 @@
         }
     };
 
+    private View.OnLongClickListener mLockToAppClickListener = new View.OnLongClickListener() {
+        @Override
+        public boolean onLongClick(View v) {
+            toggleLockedApp();
+            return true;
+        }
+    };
+
     private int mShowSearchHoldoff = 0;
     private Runnable mShowSearchPanel = new Runnable() {
         public void run() {
@@ -949,6 +916,8 @@
 
         mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
         mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
+        mNavigationBarView.getRecentsButton().setLongClickable(true);
+        mNavigationBarView.getRecentsButton().setOnLongClickListener(mLockToAppClickListener);
         mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
         updateSearchPanel();
     }
@@ -1064,18 +1033,8 @@
     }
 
     @Override
-    public void addNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
         if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
-        if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification, ranking)) {
-            // Forward the ranking so we can sort the new notification.
-            mNotificationData.updateRanking(ranking);
-            return;
-        }
-        mIntercepted.remove(notification.getKey());
-        displayNotification(notification, ranking);
-    }
-
-    public void displayNotification(StatusBarNotification notification, RankingMap ranking) {
         if (mUseHeadsUp && shouldInterrupt(notification)) {
             if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
             Entry interruptionCandidate = new Entry(notification, null);
@@ -1157,21 +1116,13 @@
     }
 
     @Override
-    public void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
-        super.updateNotificationInternal(notification, ranking);
-        // if we're here, then the notification is already in the shade
-        mIntercepted.remove(notification.getKey());
-    }
-
-    @Override
-    protected void updateRankingInternal(RankingMap ranking) {
+    protected void updateNotificationRanking(RankingMap ranking) {
         mNotificationData.updateRanking(ranking);
-        mIntercepted.retryIntercepts(ranking);
         updateNotifications();
     }
 
     @Override
-    public void removeNotificationInternal(String key, RankingMap ranking) {
+    public void removeNotification(String key, RankingMap ranking) {
         if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
                 && key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
             mHeadsUpNotificationView.clear();
@@ -1194,7 +1145,6 @@
                 animateCollapsePanels();
             }
         }
-        mIntercepted.remove(key);
         setAreThereNotifications();
     }
 
@@ -1259,7 +1209,6 @@
         for (View remove : toRemove) {
             mStackScroller.removeView(remove);
         }
-
         for (int i=0; i<toShow.size(); i++) {
             View v = toShow.get(i);
             if (v.getParent() == null) {
@@ -1292,6 +1241,7 @@
         updateRowStates();
         updateSpeedbump();
         mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup);
+        mShadeUpdates.check();
     }
 
     private void updateSpeedbump() {
@@ -1350,9 +1300,6 @@
                 // in "public" mode (atop a secure keyguard), secret notifs are totally hidden
                 continue;
             }
-            if (mIntercepted.isSyntheticEntry(ent)) {
-                continue;
-            }
             toShow.add(ent.icon);
         }
 
@@ -1387,7 +1334,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()
@@ -1465,7 +1413,7 @@
     }
 
     private int adjustDisableFlags(int state) {
-        if (mExpandedVisible) {
+        if (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
             state |= StatusBarManager.DISABLE_SYSTEM_INFO;
         }
@@ -1513,19 +1461,9 @@
         if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
             mSystemIconArea.animate().cancel();
             if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
-                mSystemIconArea.animate()
-                    .alpha(0f)
-                    .withLayer()
-                    .setDuration(160)
-                    .setInterpolator(mAlphaIn)
-                    .setListener(mMakeIconsInvisible);
+                animateStatusBarHide(mSystemIconArea);
             } else {
-                mSystemIconArea.setVisibility(View.VISIBLE);
-                mSystemIconArea.animate()
-                    .alpha(1f)
-                    .withLayer()
-                    .setInterpolator(mAlphaOut)
-                    .setDuration(320);
+                animateStatusBarShow(mSystemIconArea);
             }
         }
 
@@ -1558,25 +1496,48 @@
                 if (mTicking) {
                     haltTicker();
                 }
-
-                mNotificationIcons.animate()
-                    .alpha(0f)
-                    .withLayer()
-                    .setDuration(160)
-                    .setInterpolator(mAlphaIn)
-                    .setListener(mMakeIconsInvisible)
-                    .start();
+                animateStatusBarHide(mNotificationIcons);
             } else {
-                mNotificationIcons.setVisibility(View.VISIBLE);
-                mNotificationIcons.animate()
-                    .alpha(1f)
-                    .withLayer()
-                    .setInterpolator(mAlphaOut)
-                    .setDuration(320);
+                animateStatusBarShow(mNotificationIcons);
             }
         }
     }
 
+    /**
+     * Animates {@code v}, a view that is part of the status bar, out.
+     */
+    private void animateStatusBarHide(View v) {
+        v.animate()
+                .alpha(0f)
+                .withLayer()
+                .setDuration(160)
+                .setInterpolator(mAlphaIn)
+                .setStartDelay(0)
+                .setListener(mMakeIconsInvisible)
+                .start();
+    }
+
+    /**
+     * Animates {@code v}, a view that is part of the status bar, in.
+     */
+    private void animateStatusBarShow(View v) {
+        v.setVisibility(View.VISIBLE);
+        v.animate()
+                .alpha(1f)
+                .withLayer()
+                .setInterpolator(mAlphaOut)
+                .setDuration(320)
+                .setStartDelay(0);
+
+        // Synchronize the motion with the Keyguard fading if necessary.
+        if (mKeyguardFadingAway) {
+            v.animate()
+                    .setDuration(mKeyguardFadingAwayDuration)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .setStartDelay(mKeyguardFadingAwayDelay);
+        }
+    }
+
     @Override
     protected BaseStatusBar.H createHandler() {
         return new PhoneStatusBar.H();
@@ -1643,10 +1604,6 @@
         }
     }
 
-    private Handler getHandler() {
-        return mHandler;
-    }
-
     View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
         public void onFocusChange(View v, boolean hasFocus) {
             // Because 'v' is a ViewGroup, all its children will be (un)selected
@@ -1678,6 +1635,7 @@
         mStatusBarWindowManager.setStatusBarExpanded(true);
 
         visibilityChanged(true);
+        mWaitingForKeyguardExit = false;
         disable(mDisabledUnmodified);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
@@ -1718,7 +1676,6 @@
         }
 
         if (mStatusBarWindow != null) {
-
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
 
@@ -1791,12 +1748,6 @@
         if (false) postStartTracing();
     }
 
-    private static void cancelAnim(Animator anim) {
-        if (anim != null) {
-            anim.cancel();
-        }
-    }
-
     public void flipToNotifications(boolean animate) {
         // TODO: Animation
         mNotificationPanel.closeQs();
@@ -1872,8 +1823,8 @@
         }
 
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
-        disable(mDisabledUnmodified);
         showBouncer();
+        disable(mDisabledUnmodified);
     }
 
     public boolean interceptTouchEvent(MotionEvent event) {
@@ -1960,6 +1911,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
@@ -2014,6 +1969,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);
         }
@@ -2280,6 +2240,7 @@
         pw.println(windowStateToString(mStatusBarWindowState));
         pw.print("  mStatusBarMode=");
         pw.println(BarTransitions.modeToString(mStatusBarMode));
+        pw.print("  mDozing="); pw.println(mDozing);
         pw.print("  mZenMode=");
         pw.println(Settings.Global.zenModeToString(mZenMode));
         pw.print("  mUseHeadsUp=");
@@ -2414,28 +2375,6 @@
         }
     }
 
-    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
-        public void onClick(View v) {
-            synchronized (mNotificationData) {
-                mPostCollapseCleanup = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (DEBUG) {
-                            Log.v(TAG, "running post-collapse cleanup");
-                        }
-                        try {
-                            mBarService.onClearAllNotifications(mCurrentUserId);
-                        } catch (Exception ex) { }
-                    }
-                };
-
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-                return;
-                // TODO: Handle this better with notification stack scroller
-            }
-        }
-    };
-
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned) {
         if (onlyProvisioned && !isDeviceProvisioned()) return;
 
@@ -2463,12 +2402,6 @@
         }
     };
 
-    private View.OnClickListener mNotificationButtonListener = new View.OnClickListener() {
-        public void onClick(View v) {
-            animateExpandNotificationsPanel();
-        }
-    };
-
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -2578,6 +2511,8 @@
         if (mQSPanel != null) mQSPanel.updateResources();
 
         loadDimens();
+        mLinearOutSlowIn = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.linear_out_slow_in);
     }
 
     protected void loadDimens() {
@@ -2600,21 +2535,6 @@
 
         mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
 
-        mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
-        mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
-        mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
-        mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
-
-        mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
-        mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
-
-        mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
-        mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
-
-        mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
-
-        mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
-
         mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity);
         if (mNotificationPanelGravity <= 0) {
             mNotificationPanelGravity = Gravity.START | Gravity.TOP;
@@ -2680,7 +2600,6 @@
         if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
             return;
         }
-
         String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]);
         String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]);
         try {
@@ -2905,6 +2824,27 @@
         updateKeyguardState();
     }
 
+    /**
+     * Notifies the status bar the Keyguard is fading away with the specified timings.
+     *
+     * @param delay the animation delay in miliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
+     */
+    public void setKeyguardFadingAway(long delay, long fadeoutDuration) {
+        mKeyguardFadingAway = true;
+        mKeyguardFadingAwayDelay = delay;
+        mKeyguardFadingAwayDuration = fadeoutDuration;
+        mWaitingForKeyguardExit = false;
+        disable(mDisabledUnmodified);
+    }
+
+    /**
+     * Notifies that the Keyguard fading away animation is done.
+     */
+    public void finishKeyguardFadingAway() {
+        mKeyguardFadingAway = false;
+    }
+
     private void updatePublicMode() {
         setLockscreenPublicMode(
                 (mStatusBarKeyguardViewManager.isShowing() || 
@@ -2917,9 +2857,11 @@
             mKeyguardStatusView.setVisibility(View.VISIBLE);
             mKeyguardIndicationController.setVisible(true);
             mNotificationPanel.resetViews();
+            mKeyguardUserSwitcher.setKeyguard(true);
         } else {
             mKeyguardStatusView.setVisibility(View.GONE);
             mKeyguardIndicationController.setVisible(false);
+            mKeyguardUserSwitcher.setKeyguard(false);
         }
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
@@ -2932,6 +2874,7 @@
             mNotificationPanel.setKeyguardShowing(false);
             mScrimController.setKeyguardShowing(false);
         }
+        updateDozingState();
         updateStackScrollerState();
         updatePublicMode();
         updateNotifications();
@@ -2939,6 +2882,26 @@
         updateCarrierLabelVisibility(false);
     }
 
+    private void updateDozingState() {
+        final boolean bottomGone = mKeyguardBottomArea.getVisibility() == View.GONE;
+        if (mDozing) {
+            mNotificationPanel.setBackgroundColor(0xff000000);
+            mHeader.setVisibility(View.INVISIBLE);
+            if (!bottomGone) {
+                mKeyguardBottomArea.setVisibility(View.INVISIBLE);
+            }
+            mStackScroller.setDark(true, false /*animate*/);
+        } else {
+            mNotificationPanel.setBackground(null);
+            mHeader.setVisibility(View.VISIBLE);
+            if (!bottomGone) {
+                mKeyguardBottomArea.setVisibility(View.VISIBLE);
+            }
+            mStackScroller.setDark(false, false /*animate*/);
+        }
+        mScrimController.setDozing(mDozing);
+    }
+
     public void updateStackScrollerState() {
         if (mStackScroller == null) return;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
@@ -2979,6 +2942,7 @@
 
     private void showBouncer() {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+            mWaitingForKeyguardExit = true;
             mStatusBarKeyguardViewManager.dismiss();
         }
     }
@@ -3129,6 +3093,12 @@
         mSystemIconArea.addView(mSystemIcons, 0);
     }
 
+    @Override
+    public void setBouncerShowing(boolean bouncerShowing) {
+        super.setBouncerShowing(bouncerShowing);
+        disable(mDisabledUnmodified);
+    }
+
     public void onScreenTurnedOff() {
         mStackScroller.setAnimationsEnabled(false);
     }
@@ -3137,6 +3107,28 @@
         mStackScroller.setAnimationsEnabled(true);
     }
 
+    public void toggleLockedApp() {
+        Log.d(TAG, "Trying to toggle lock-to-app");
+        try {
+            IActivityManager activityManager = ActivityManagerNative.getDefault();
+            if (activityManager.isInLockTaskMode()) {
+                activityManager.stopLockTaskModeOnCurrent();
+            } else {
+                try {
+                    boolean lockToAppEnabled = Settings.System.getInt(mContext.getContentResolver(),
+                            Settings.System.LOCK_TO_APP_ENABLED) != 0;
+                    if (lockToAppEnabled) {
+                        activityManager.startLockTaskModeOnCurrent();
+                    }
+                } catch (SettingNotFoundException e) {
+                    // No setting, not enabled.
+                }
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Unable to toggle Lock-to-app", e);
+        }
+    }
+
     private final Runnable mUserActivity = new Runnable() {
         @Override
         public void run() {
@@ -3145,4 +3137,158 @@
             }
         }
     };
+
+    // 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);
+    }
+
+    private final class ShadeUpdates {
+        private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
+        private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
+
+        public void check() {
+            mNewVisibleNotifications.clear();
+            for (int i = 0; i < mNotificationData.size(); i++) {
+                final Entry entry = mNotificationData.get(i);
+                final boolean visible = entry.row != null
+                        && entry.row.getVisibility() == View.VISIBLE;
+                if (visible) {
+                    mNewVisibleNotifications.add(entry.key + entry.notification.getPostTime());
+                }
+            }
+            final boolean updates = !mVisibleNotifications.containsAll(mNewVisibleNotifications);
+            mVisibleNotifications.clear();
+            mVisibleNotifications.putAll(mNewVisibleNotifications);
+
+            // We have new notifications
+            if (updates && mDozeServiceHost != null) {
+                mDozeServiceHost.fireNewNotifications();
+            }
+        }
+    }
+
+    private final class DozeServiceHost implements DozeService.Host {
+        // Amount of time to allow to update the time shown on the screen before releasing
+        // the wakelock.  This timeout is design to compensate for the fact that we don't
+        // currently have a way to know when time display contents have actually been
+        // refreshed once we've finished rendering a new frame.
+        private static final long PROCESSING_TIME = 500;
+
+        private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+        private final H mHandler = new H();
+
+        private DozeService mCurrentDozeService;
+
+        public void fireNewNotifications() {
+            for (Callback callback : mCallbacks) {
+                callback.onNewNotifications();
+            }
+        }
+
+        @Override
+        public void addCallback(Callback callback) {
+            mCallbacks.add(callback);
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+            mCallbacks.remove(callback);
+        }
+
+        @Override
+        public void requestDoze(DozeService dozeService) {
+            if (dozeService == null) return;
+            dozeService.stayAwake(PROCESSING_TIME);
+            mHandler.obtainMessage(H.REQUEST_DOZE, dozeService).sendToTarget();
+        }
+
+        @Override
+        public void requestTease(DozeService dozeService) {
+            if (dozeService == null) return;
+            dozeService.stayAwake(PROCESSING_TIME);
+            mHandler.obtainMessage(H.REQUEST_TEASE, dozeService).sendToTarget();
+        }
+
+        @Override
+        public void dozingStopped(DozeService dozeService) {
+            if (dozeService == null) return;
+            dozeService.stayAwake(PROCESSING_TIME);
+            mHandler.obtainMessage(H.DOZING_STOPPED, dozeService).sendToTarget();
+        }
+
+        private void handleRequestDoze(DozeService dozeService) {
+            mCurrentDozeService = dozeService;
+            if (!mDozing) {
+                mDozing = true;
+                updateDozingState();
+            }
+            mCurrentDozeService.startDozing();
+        }
+
+        private void handleRequestTease(DozeService dozeService) {
+            if (!dozeService.equals(mCurrentDozeService)) return;
+            final long stayAwake = mScrimController.tease();
+            mCurrentDozeService.stayAwake(stayAwake);
+        }
+
+        private void handleDozingStopped(DozeService dozeService) {
+            if (dozeService.equals(mCurrentDozeService)) {
+                mCurrentDozeService = null;
+            }
+            if (mDozing) {
+                mDozing = false;
+                updateDozingState();
+            }
+        }
+
+        private final class H extends Handler {
+            private static final int REQUEST_DOZE = 1;
+            private static final int REQUEST_TEASE = 2;
+            private static final int DOZING_STOPPED = 3;
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == REQUEST_DOZE) {
+                    handleRequestDoze((DozeService) msg.obj);
+                } else if (msg.what == REQUEST_TEASE) {
+                    handleRequestTease((DozeService) msg.obj);
+                } else if (msg.what == DOZING_STOPPED) {
+                    handleDozingStopped((DozeService) msg.obj);
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1264d75..bf63f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -21,21 +21,35 @@
 import android.animation.ValueAnimator;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import com.android.systemui.R;
+
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
  */
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
+    private static final String TAG = "ScrimController";
+    private static final boolean DEBUG = false;
 
     private static final float SCRIM_BEHIND_ALPHA = 0.62f;
     private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.5f;
     private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
     private static final long ANIMATION_DURATION = 220;
+    private static final int TAG_KEY_ANIM = R.id.scrim;
+
+    private static final int NUM_TEASES = 3;
+    private static final long TEASE_IN_ANIMATION_DURATION = 500;
+    private static final long TEASE_VISIBLE_DURATION = 3000;
+    private static final long TEASE_OUT_ANIMATION_DURATION = 1000;
+    private static final long TEASE_INVISIBLE_DURATION = 1000;
+    private static final long TEASE_DURATION = TEASE_IN_ANIMATION_DURATION
+            + TEASE_VISIBLE_DURATION + TEASE_OUT_ANIMATION_DURATION + TEASE_INVISIBLE_DURATION;
 
     private final View mScrimBehind;
     private final View mScrimInFront;
@@ -54,6 +68,8 @@
     private long mAnimationDelay;
     private Runnable mOnAnimationFinished;
     private boolean mAnimationStarted;
+    private boolean mDozing;
+    private int mTeasesRemaining;
 
     private final Interpolator mInterpolator = new DecelerateInterpolator();
 
@@ -97,6 +113,29 @@
         scheduleUpdate();
     }
 
+    public void setDozing(boolean dozing) {
+        if (mDozing == dozing) return;
+        mDozing = dozing;
+        if (!mDozing) {
+            cancelTeasing();
+        }
+        scheduleUpdate();
+    }
+
+    /** When dozing, fade screen contents in and out a few times using the front scrim. */
+    public long tease() {
+        if (!mDozing) return 0;
+        mTeasesRemaining = NUM_TEASES;
+        mScrimInFront.post(mTeaseIn);
+        return NUM_TEASES * TEASE_DURATION;
+    }
+
+    private void cancelTeasing() {
+        mTeasesRemaining = 0;
+        mScrimInFront.removeCallbacks(mTeaseIn);
+        mScrimInFront.removeCallbacks(mTeaseOut);
+    }
+
     private void scheduleUpdate() {
         if (mUpdatePending) return;
 
@@ -125,6 +164,8 @@
         } else if (mBouncerShowing) {
             setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
             setScrimBehindColor(0f);
+        } else if (mDozing) {
+            setScrimInFrontColor(1);
         } else {
             setScrimInFrontColor(0f);
             setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD);
@@ -174,6 +215,10 @@
         if (current == targetColor) {
             return;
         }
+        Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
+        if (runningAnim instanceof ValueAnimator) {
+            ((ValueAnimator) runningAnim).cancel();
+        }
         ValueAnimator anim = ValueAnimator.ofInt(current, target);
         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
@@ -193,9 +238,11 @@
                     mOnAnimationFinished.run();
                     mOnAnimationFinished = null;
                 }
+                scrim.setTag(TAG_KEY_ANIM, null);
             }
         });
         anim.start();
+        scrim.setTag(TAG_KEY_ANIM, anim);
         mAnimationStarted = true;
     }
 
@@ -225,4 +272,51 @@
         mAnimationStarted = false;
         return true;
     }
+
+    private final Runnable mTeaseIn = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(TAG, "Tease in, mDozing=" + mDozing
+                    + " mTeasesRemaining=" + mTeasesRemaining);
+            if (!mDozing || mTeasesRemaining == 0) return;
+            mTeasesRemaining--;
+            mDurationOverride = TEASE_IN_ANIMATION_DURATION;
+            mAnimationDelay = 0;
+            mAnimateChange = true;
+            mOnAnimationFinished = mTeaseInFinished;
+            setScrimColor(mScrimInFront, 0);
+        }
+    };
+
+    private final Runnable mTeaseInFinished = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+            if (!mDozing) return;
+            mScrimInFront.postDelayed(mTeaseOut, TEASE_VISIBLE_DURATION);
+        }
+    };
+
+    private final Runnable mTeaseOut = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+            if (!mDozing) return;
+            mDurationOverride = TEASE_OUT_ANIMATION_DURATION;
+            mAnimationDelay = 0;
+            mAnimateChange = true;
+            mOnAnimationFinished = mTeaseOutFinished;
+            setScrimColor(mScrimInFront, 1);
+        }
+    };
+
+    private final Runnable mTeaseOutFinished = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(TAG, "Tease out finished, mTeasesRemaining=" + mTeasesRemaining);
+            if (mTeasesRemaining > 0) {
+                mScrimInFront.postDelayed(mTeaseIn, TEASE_INVISIBLE_DURATION);
+            }
+        }
+    };
 }
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..fc10a08 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,12 @@
     private View mSignalCluster;
     private View mSettingsButton;
     private View mBrightnessContainer;
+    private View mEmergencyCallsOnly;
+    private TextView mChargingInfo;
+
+    private boolean mShowEmergencyCallsOnly;
+    private boolean mShowChargingInfo;
+    private boolean mKeyguardUserSwitcherShowing;
 
     private int mCollapsedHeight;
     private int mExpandedHeight;
@@ -66,6 +73,7 @@
     private int mNormalWidth;
     private int mPadding;
     private int mMultiUserExpandedMargin;
+    private int mSystemIconsSwitcherHiddenExpandedMargin;
 
     private ActivityStarter mActivityStarter;
     private BrightnessController mBrightnessController;
@@ -93,6 +101,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() {
@@ -117,7 +127,8 @@
         mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding);
         mMultiUserExpandedMargin =
                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
-
+        mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize(
+                R.dimen.system_icons_switcher_hidden_expanded_margin);
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -204,13 +215,40 @@
         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);
+        mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing
+                ? 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;
+        lp.setMarginEnd(0);
+        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);
+            if (mMultiUserSwitch.getVisibility() == GONE) {
+                lp.setMarginEnd(mSystemIconsSwitcherHiddenExpandedMargin);
+            }
+        }
+        mSystemIconsContainer.setLayoutParams(lp);
+
+        RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams();
+        if (systemIconsAboveClock) {
+            clockLp.addRule(BELOW, mChargingInfo.getId());
+        } else {
+            clockLp.addRule(BELOW, mEmergencyCallsOnly.getId());
+        }
+        mDateTime.setLayoutParams(clockLp);
     }
 
     private void updateBrightnessControllerState() {
@@ -338,4 +376,31 @@
     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);
+    }
+
+    public void setKeyguardUserSwitcherShowing(boolean showing) {
+        // STOPSHIP: NOT CALLED PROPERLY WHEN GOING TO FULL SHADE AND RETURNING!?!
+        mKeyguardUserSwitcherShowing = showing;
+        updateVisibilities();
+        updateSystemIconsLayoutParams();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09e4d94..a36f3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -190,19 +190,23 @@
      */
     public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
-        mPhoneStatusBar.hideKeyguard();
-        mStatusBarWindowManager.setKeyguardFadingAway(true);
-        mStatusBarWindowManager.setKeyguardShowing(false);
+
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = startTime - uptimeMillis;
         if (delay < 0) {
             delay = 0;
         }
+
+        mPhoneStatusBar.setKeyguardFadingAway(delay, fadeoutDuration);
+        mPhoneStatusBar.hideKeyguard();
+        mStatusBarWindowManager.setKeyguardFadingAway(true);
+        mStatusBarWindowManager.setKeyguardShowing(false);
         mBouncer.animateHide(delay, fadeoutDuration);
         mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
             @Override
             public void run() {
                 mStatusBarWindowManager.setKeyguardFadingAway(false);
+                mPhoneStatusBar.finishKeyguardFadingAway();
             }
         });
         mViewMediatorCallback.keyguardGone();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
new file mode 100644
index 0000000..6f2642a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -0,0 +1,219 @@
+/*
+ * 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.statusbar.phone;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view that displays a user image cropped to a circle with a frame.
+ */
+public class UserAvatarView extends View {
+
+    private int mActiveFrameColor;
+    private int mFrameColor;
+    private float mFrameWidth;
+    private Bitmap mBitmap;
+    private Drawable mDrawable;
+
+    private final Paint mFramePaint = new Paint();
+    private final Paint mBitmapPaint = new Paint();
+    private final Matrix mDrawMatrix = new Matrix();
+
+    private float mScale = 1;
+
+    public UserAvatarView(Context context, AttributeSet attrs,
+            int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.UserAvatarView_frameWidth:
+                    setFrameWidth(a.getDimension(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_activeFrameColor:
+                    setActiveFrameColor(a.getColor(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_frameColor:
+                    setFrameColor(a.getColor(attr, 0));
+                    break;
+            }
+        }
+        a.recycle();
+
+        mFramePaint.setAntiAlias(true);
+        mFramePaint.setStyle(Paint.Style.STROKE);
+        mBitmapPaint.setAntiAlias(true);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserAvatarView(Context context) {
+        this(context, null);
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        setDrawable(null);
+        mBitmap = bitmap;
+        if (mBitmap != null) {
+            mBitmapPaint.setShader(new BitmapShader(
+                    bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+        } else {
+            mBitmapPaint.setShader(null);
+        }
+        configureBounds();
+        invalidate();
+    }
+
+    public void setFrameColor(int frameColor) {
+        mFrameColor = frameColor;
+        invalidate();
+    }
+
+    public void setActiveFrameColor(int activeFrameColor) {
+        mActiveFrameColor = activeFrameColor;
+        invalidate();
+    }
+
+    public void setFrameWidth(float frameWidth) {
+        mFrameWidth = frameWidth;
+        invalidate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        configureBounds();
+    }
+
+    public void configureBounds() {
+        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+
+        int dwidth;
+        int dheight;
+        if (mBitmap != null) {
+            dwidth = mBitmap.getWidth();
+            dheight = mBitmap.getHeight();
+        } else if (mDrawable != null) {
+            dwidth = mDrawable.getIntrinsicWidth();
+            dheight = mDrawable.getIntrinsicHeight();
+            mDrawable.setBounds(0, 0, dwidth, dheight);
+            vwidth -= 2 * (mFrameWidth - 1);
+            vheight -= 2 * (mFrameWidth - 1);
+        } else {
+            return;
+        }
+
+        float scale;
+        float dx;
+        float dy;
+
+        scale = Math.min((float) vwidth / (float) dwidth,
+                (float) vheight / (float) dheight);
+
+        dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
+        dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
+
+        mDrawMatrix.setScale(scale, scale);
+        mDrawMatrix.postTranslate(dx, dy);
+        mScale = scale;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int frameColor = isActivated() ? mActiveFrameColor : mFrameColor;
+        float halfW = getWidth() / 2f;
+        float halfH = getHeight() / 2f;
+        float halfSW = Math.min(halfH, halfW);
+        if (mBitmap != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.concat(mDrawMatrix);
+            float halfBW = mBitmap.getWidth() / 2f;
+            float halfBH = mBitmap.getHeight() / 2f;
+            float halfBSW = Math.min(halfBH, halfBW);
+            canvas.drawCircle(halfBW, halfBH, halfBSW - mFrameWidth / mScale + 1, mBitmapPaint);
+            canvas.restoreToCount(saveCount);
+        } else if (mDrawable != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.translate(mFrameWidth - 1, mFrameWidth - 1);
+            canvas.concat(mDrawMatrix);
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+        if (frameColor != 0) {
+            mFramePaint.setColor(frameColor);
+            mFramePaint.setStrokeWidth(mFrameWidth);
+            canvas.drawCircle(halfW, halfH, halfSW - mFrameWidth / 2f, mFramePaint);
+        }
+    }
+
+    public void setDrawable(Drawable d) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+            unscheduleDrawable(mDrawable);
+        }
+        mDrawable = d;
+        if (d != null) {
+            d.setCallback(this);
+            if (d.isStateful()) {
+                d.setState(getDrawableState());
+            }
+            d.setLayoutDirection(getLayoutDirection());
+            configureBounds();
+        }
+        if (d != null) {
+            mBitmap = null;
+        }
+        configureBounds();
+        invalidate();
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (dr == mDrawable) {
+            invalidate();
+        } else {
+            super.invalidateDrawable(dr);
+        }
+    }
+}
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/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 330b599..0134fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -63,6 +63,7 @@
                     // Just an old-fashioned ImageView
                     performLongClick();
                 }
+                setPressed(false);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
new file mode 100644
index 0000000..c90750c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -0,0 +1,209 @@
+/*
+ * 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.statusbar.policy;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.WindowManagerGlobal;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+public class KeyguardUserSwitcher implements View.OnClickListener {
+
+    private static final String TAG = "KeyguardUserSwitcher";
+
+    private final Context mContext;
+    private final ViewGroup mUserSwitcher;
+    private final UserManager mUserManager;
+    private final StatusBarHeaderView mHeader;
+
+    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
+            StatusBarHeaderView header) {
+        mContext = context;
+        if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher)) {
+            mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            mHeader = header;
+            refresh();
+        } else {
+            mUserSwitcher = null;
+            mUserManager = null;
+            mHeader = null;
+        }
+    }
+
+    public void setKeyguard(boolean keyguard) {
+        if (mUserSwitcher != null) {
+            // TODO: Cache showUserSwitcherOnKeyguard().
+            if (keyguard && showUserSwitcherOnKeyguard()) {
+                show();
+                refresh();
+            } else {
+                hide();
+            }
+        }
+    }
+
+    /**
+     * @return true if the user switcher should be shown on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    private boolean showUserSwitcherOnKeyguard() {
+        // TODO: Set isEdu. The edu provisioning process can add settings to Settings.Global.
+        boolean isEdu = false;
+        if (isEdu) {
+            return true;
+        }
+        List<UserInfo> users = mUserManager.getUsers(true /* excludeDying */);
+        int N = users.size();
+        int switchableUsers = 0;
+        for (int i = 0; i < N; i++) {
+            if (users.get(i).supportsSwitchTo()) {
+                switchableUsers++;
+            }
+        }
+        return switchableUsers > 1;
+    }
+
+    public void show() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.VISIBLE);
+            mHeader.setKeyguardUserSwitcherShowing(true);
+        }
+    }
+
+    private void hide() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.GONE);
+            mHeader.setKeyguardUserSwitcherShowing(false);
+        }
+    }
+
+    private void refresh() {
+        if (mUserSwitcher != null) {
+            new AsyncTask<Void, Void, ArrayList<UserData>>() {
+                @Override
+                protected ArrayList<UserData> doInBackground(Void... params) {
+                    return loadUsers();
+                }
+
+                @Override
+                protected void onPostExecute(ArrayList<UserData> userInfos) {
+                    bind(userInfos);
+                }
+            }.execute((Void[]) null);
+        }
+    }
+
+    private void bind(ArrayList<UserData> userList) {
+        mUserSwitcher.removeAllViews();
+        int N = userList.size();
+        for (int i = 0; i < N; i++) {
+            mUserSwitcher.addView(inflateUser(userList.get(i)));
+        }
+        // TODO: add Guest
+        // TODO: add (+) button
+    }
+
+    private View inflateUser(UserData user) {
+        View v = LayoutInflater.from(mUserSwitcher.getContext()).inflate(
+                R.layout.keyguard_user_switcher_item, mUserSwitcher, false);
+        TextView name = (TextView) v.findViewById(R.id.name);
+        UserAvatarView picture = (UserAvatarView) v.findViewById(R.id.picture);
+        name.setText(user.userInfo.name);
+        picture.setActivated(user.isCurrent);
+        if (user.userInfo.isGuest()) {
+            picture.setDrawable(mContext.getResources().getDrawable(R.drawable.ic_account_circle));
+        } else {
+            picture.setBitmap(user.userIcon);
+        }
+        v.setOnClickListener(this);
+        v.setTag(user.userInfo);
+        // TODO: mark which user is current for accessibility.
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        switchUser(((UserInfo)v.getTag()).id);
+    }
+
+    // TODO: Factor out logic below and share with QS implementation.
+
+    private ArrayList<UserData> loadUsers() {
+        ArrayList<UserInfo> users = (ArrayList<UserInfo>) mUserManager
+                .getUsers(true /* excludeDying */);
+        int N = users.size();
+        ArrayList<UserData> result = new ArrayList<>(N);
+        int currentUser = -1;
+        try {
+            currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couln't get current user.", e);
+        }
+        for (int i = 0; i < N; i++) {
+            UserInfo user = users.get(i);
+            if (user.supportsSwitchTo()) {
+                boolean isCurrent = user.id == currentUser;
+                result.add(new UserData(user, mUserManager.getUserIcon(user.id), isCurrent));
+            }
+        }
+        return result;
+    }
+
+    private void switchUser(int userId) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            ActivityManagerNative.getDefault().switchUser(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't switch user.", e);
+        }
+    }
+
+    private static class UserData {
+        final UserInfo userInfo;
+        final Bitmap userIcon;
+        final boolean isCurrent;
+
+        UserData(UserInfo userInfo, Bitmap userIcon, boolean isCurrent) {
+            this.userInfo = userInfo;
+            this.userIcon = userIcon;
+            this.isCurrent = isCurrent;
+        }
+    }
+}
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/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 6d92b05..0582140 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -32,6 +32,8 @@
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
     private int mSpeedBumpIndex = -1;
+    private float mScrimAmount;
+    private boolean mDark;
 
     public int getScrollY() {
         return mScrollY;
@@ -61,6 +63,11 @@
         mDimmed = dimmed;
     }
 
+    /** In dark mode, we draw as little as possible, assuming a black background */
+    public void setDark(boolean dark) {
+        mDark = dark;
+    }
+
     /**
      * In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
      * interaction. This child is then scaled normally and its background is fully opaque.
@@ -73,6 +80,10 @@
         return mDimmed;
     }
 
+    public boolean isDark() {
+        return mDark;
+    }
+
     public ActivatableNotificationView getActivatedChild() {
         return mActivatedChild;
     }
@@ -85,6 +96,14 @@
         }
     }
 
+    public void setScrimAmount(float scrimAmount) {
+        mScrimAmount = scrimAmount;
+    }
+
+    public float getScrimAmount() {
+        return mScrimAmount;
+    }
+
     public float getOverScrollAmount(boolean top) {
         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index cf56fa57..99d3a01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -29,6 +29,7 @@
     boolean animateHeight;
     boolean animateTopInset;
     boolean animateDimmed;
+    boolean animateDark;
     boolean hasDelays;
 
     public AnimationFilter animateAlpha() {
@@ -71,6 +72,11 @@
         return this;
     }
 
+    public AnimationFilter animateDark() {
+        animateDark = true;
+        return this;
+    }
+
     /**
      * Combines multiple filters into {@code this} filter, using or as the operand .
      *
@@ -92,6 +98,7 @@
         animateHeight |= filter.animateHeight;
         animateTopInset |= filter.animateTopInset;
         animateDimmed |= filter.animateDimmed;
+        animateDark |= filter.animateDark;
         hasDelays |= filter.hasDelays;
     }
 
@@ -103,6 +110,7 @@
         animateHeight = false;
         animateTopInset = false;
         animateDimmed = false;
+        animateDark = false;
         hasDelays = false;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 4e1b686..4220efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -128,6 +128,7 @@
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
     private boolean mDimmedNeedsAnimation;
+    private boolean mDarkNeedsAnimation;
     private boolean mActivateNeedsAnimation;
     private boolean mIsExpanded = true;
     private boolean mChildrenUpdateRequested;
@@ -356,14 +357,6 @@
         return getNotGoneChildCount() > 1;
     }
 
-    private boolean isViewExpanded(View view) {
-        if (view != null) {
-            ExpandableView expandView = (ExpandableView) view;
-            return expandView.getActualHeight() > mCollapsedSize;
-        }
-        return false;
-    }
-
     /**
      * Updates the children views according to the stack scroll algorithm. Call this whenever
      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
@@ -541,8 +534,9 @@
             if (slidingChild.getVisibility() == GONE) {
                 continue;
             }
-            float top = slidingChild.getTranslationY();
-            float bottom = top + slidingChild.getActualHeight();
+            float childTop = slidingChild.getTranslationY();
+            float top = childTop + slidingChild.getClipTopAmount();
+            float bottom = childTop + slidingChild.getActualHeight();
             int left = slidingChild.getLeft();
             int right = slidingChild.getRight();
 
@@ -762,11 +756,12 @@
                                 }
                             }
                         }
-
-                        mActivePointerId = INVALID_POINTER;
-                        endDrag();
                     }
+
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
                 }
+
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
@@ -968,7 +963,7 @@
      * @param animate Should an animation be performed.
      */
     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
-        setOverScrollAmount(numPixels * getRubberBandFactor(), onTop, animate, true);
+        setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
     }
 
     /**
@@ -1004,7 +999,7 @@
         if (animate) {
             mStateAnimator.animateOverScrollToAmount(amount, onTop);
         } else {
-            setOverScrolledPixels(amount / getRubberBandFactor(), onTop);
+            setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
             mAmbientState.setOverScrollAmount(amount, onTop);
             if (onTop) {
                 notifyOverscrollTopListener(amount);
@@ -1226,13 +1221,14 @@
                 mOwnScrollY -= (int) topAmount;
                 mDontReportNextOverScroll = true;
                 setOverScrollAmount(0, true, false);
-                mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor()
+                mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
                         * mOverflingDistance + topAmount;
             } else if (velocityY > 0 && bottomAmount > 0) {
                 mOwnScrollY += bottomAmount;
                 setOverScrollAmount(0, false, false);
-                mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor()
-                        * mOverflingDistance + bottomAmount;
+                mMaxOverScroll = Math.abs(velocityY) / 1000f
+                        * getRubberBandFactor(false /* onTop */) * mOverflingDistance
+                        +  bottomAmount;
             } else {
                 // it will be set once we reach the boundary
                 mMaxOverScroll = 0.0f;
@@ -1274,7 +1270,10 @@
         return Math.max(desiredPadding, mIntrinsicPadding);
     }
 
-    private float getRubberBandFactor() {
+    private float getRubberBandFactor(boolean onTop) {
+        if (!onTop) {
+            return RUBBER_BAND_FACTOR_NORMAL;
+        }
         if (mExpandedInThisMotion) {
             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
         } else if (mIsExpansionChanging) {
@@ -1473,6 +1472,7 @@
         generateTopPaddingEvent();
         generateActivateEvent();
         generateDimmedEvent();
+        generateDarkEvent();
         mNeedsAnimation = false;
     }
 
@@ -1548,6 +1548,14 @@
         mDimmedNeedsAnimation = false;
     }
 
+    private void generateDarkEvent() {
+        if (mDarkNeedsAnimation) {
+            mAnimationEvents.add(
+                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
+        }
+        mDarkNeedsAnimation = false;
+    }
+
     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
         if (!isScrollingEnabled()) {
             return false;
@@ -1840,6 +1848,23 @@
         return true;
     }
 
+    public void setScrimAlpha(float progress) {
+        mAmbientState.setScrimAmount(progress);
+        requestChildrenUpdate();
+    }
+
+    /**
+     * See {@link AmbientState#setDark}.
+     */
+    public void setDark(boolean dark, boolean animate) {
+        mAmbientState.setDark(dark);
+        if (animate && mAnimationsEnabled) {
+            mDarkNeedsAnimation = true;
+            mNeedsAnimation =  true;
+        }
+        requestChildrenUpdate();
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
@@ -1929,7 +1954,11 @@
                         .animateHeight()
                         .animateTopInset()
                         .animateY()
-                        .animateZ()
+                        .animateZ(),
+
+                // ANIMATION_TYPE_DARK
+                new AnimationFilter()
+                        .animateDark(),
         };
 
         static int[] LENGTHS = new int[] {
@@ -1960,6 +1989,9 @@
 
                 // ANIMATION_TYPE_CHANGE_POSITION
                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+                // ANIMATION_TYPE_DARK
+                StackStateAnimator.ANIMATION_DURATION_STANDARD,
         };
 
         static final int ANIMATION_TYPE_ADD = 0;
@@ -1971,6 +2003,7 @@
         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
         static final int ANIMATION_TYPE_DIMMED = 7;
         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
+        static final int ANIMATION_TYPE_DARK = 9;
 
         final long eventStartTime;
         final View changingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index cbad9dc..4956fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -154,6 +154,17 @@
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivated(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState);
+        updateScrimAmount(resultState, algorithmState, ambientState.getScrimAmount());
+    }
+
+    private void updateScrimAmount(StackScrollState resultState,
+            StackScrollAlgorithmState algorithmState, float scrimAmount) {
+        int childCount = algorithmState.visibleChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            View child = algorithmState.visibleChildren.get(i);
+            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+            childViewState.scrimAmount = scrimAmount;
+        }
     }
 
     private void updateClipping(StackScrollState resultState,
@@ -222,12 +233,14 @@
     private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
             StackScrollAlgorithmState algorithmState) {
         boolean dimmed = ambientState.isDimmed();
+        boolean dark = ambientState.isDark();
         View activatedChild = ambientState.getActivatedChild();
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
             StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
             childViewState.dimmed = dimmed;
+            childViewState.dark = dark;
             boolean isActivatedChild = activatedChild == child;
             childViewState.scale = !dimmed || isActivatedChild
                     ? 1.0f
@@ -587,6 +600,7 @@
                     algorithmState.itemsInTopStack += algorithmState.partialInTop;
                     newSize = Math.max(mCollapsedSize, newSize);
                     if (i == 0) {
+                        algorithmState.itemsInTopStack = 1.0f;
                         childViewState.height = (int) newSize;
                     }
                     algorithmState.lastTopStackIndex = i;
@@ -617,6 +631,20 @@
             if (i < algorithmState.itemsInTopStack) {
                 float stackIndex = algorithmState.itemsInTopStack - i;
                 stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2);
+                if (i == 0 && algorithmState.itemsInTopStack < 2.0f) {
+
+                    // We only have the top item and an additional item in the top stack,
+                    // Interpolate the index from 0 to 2 while the second item is
+                    // translating in.
+                    stackIndex -= 1.0f;
+                    if (algorithmState.scrollY > mCollapsedSize) {
+
+                        // Since there is a shadow treshhold, we cant just interpolate from 0 to
+                        // 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
+                        // height will not be noticable since we have padding in between.
+                        stackIndex = 0.1f + stackIndex * 1.9f;
+                    }
+                }
                 childViewState.zTranslation = mZBasicHeight
                         + stackIndex * mZDistanceBetweenElements;
             } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 1ad4acc..d8407d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -148,6 +148,12 @@
                 // apply dimming
                 child.setDimmed(state.dimmed, false /* animate */);
 
+                // apply dark
+                child.setDark(state.dark, false /* animate */);
+
+                // apply scrimming
+                child.setScrimAmount(state.scrimAmount);
+
                 float oldClipTopAmount = child.getClipTopAmount();
                 if (oldClipTopAmount != state.clipTopAmount) {
                     child.setClipTopAmount(state.clipTopAmount);
@@ -221,6 +227,13 @@
         boolean gone;
         float scale;
         boolean dimmed;
+        boolean dark;
+
+        /**
+         * A value between 0 and 1 indicating how much the view should be scrimmed.
+         * 1 means that the notifications will be darkened as much as possible.
+         */
+        float scrimAmount;
 
         /**
          * The amount which the view should be clipped from the top. This is calculated to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 225398a..5efbc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -176,6 +176,12 @@
         // start dimmed animation
         child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
 
+        // start dark animation
+        child.setDark(viewState.dark, mAnimationFilter.animateDark);
+
+        // apply scrimming
+        child.setScrimAmount(viewState.scrimAmount);
+
         if (wasAdded) {
             child.performAddAnimation(delay);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 9260aac..e354166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -47,27 +47,15 @@
     }
 
     @Override
-    public void addNotification(StatusBarNotification notification) {
+    public void addNotification(StatusBarNotification notification, RankingMap ranking) {
     }
 
     @Override
-    public void addNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+    protected void updateNotificationRanking(RankingMap ranking) {
     }
 
     @Override
-    protected void updateRankingInternal(RankingMap ranking) {
-    }
-
-    @Override
-    public void updateNotification(StatusBarNotification notification) {
-    }
-
-    @Override
-    public void removeNotificationInternal(String key, RankingMap ranking) {
-    }
-
-    @Override
-    public void removeNotification(String key) {
+    public void removeNotification(String key, RankingMap ranking) {
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 1cab7ea..d514c99 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -34,6 +34,9 @@
 import android.media.AudioSystem;
 import android.media.RingtoneManager;
 import android.media.ToneGenerator;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaController.VolumeInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -224,6 +227,7 @@
     /** Object that contains data for each slider */
     private class StreamControl {
         int streamType;
+        MediaController controller;
         ViewGroup group;
         ImageView icon;
         SeekBar seekbarView;
@@ -405,7 +409,8 @@
         if (streamType == STREAM_MASTER) {
             return mAudioManager.isMasterMute();
         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
-            return (mAudioManager.getRemoteStreamVolume() <= 0);
+            // TODO do we need to support a distinct mute property for remote?
+            return false;
         } else {
             return mAudioManager.isStreamMute(streamType);
         }
@@ -415,7 +420,14 @@
         if (streamType == STREAM_MASTER) {
             return mAudioManager.getMasterMaxVolume();
         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
-            return mAudioManager.getRemoteStreamMaxVolume();
+            if (mStreamControls != null) {
+                StreamControl sc = mStreamControls.get(streamType);
+                if (sc != null && sc.controller != null) {
+                    VolumeInfo vi = sc.controller.getVolumeInfo();
+                    return vi.getMaxVolume();
+                }
+            }
+            return -1;
         } else {
             return mAudioManager.getStreamMaxVolume(streamType);
         }
@@ -425,19 +437,32 @@
         if (streamType == STREAM_MASTER) {
             return mAudioManager.getMasterVolume();
         } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
-            return mAudioManager.getRemoteStreamVolume();
+            if (mStreamControls != null) {
+                StreamControl sc = mStreamControls.get(streamType);
+                if (sc != null && sc.controller != null) {
+                    VolumeInfo vi = sc.controller.getVolumeInfo();
+                    return vi.getCurrentVolume();
+                }
+            }
+            return -1;
         } else {
             return mAudioManager.getStreamVolume(streamType);
         }
     }
 
-    private void setStreamVolume(int streamType, int index, int flags) {
-        if (streamType == STREAM_MASTER) {
-            mAudioManager.setMasterVolume(index, flags);
-        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
-            mAudioManager.setRemoteStreamVolume(index);
-        } else {
-            mAudioManager.setStreamVolume(streamType, index, flags);
+    private void setStreamVolume(StreamControl sc, int index, int flags) {
+        if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+            if (sc.controller != null) {
+                sc.controller.setVolumeTo(index, flags);
+            } else {
+                Log.wtf(mTag, "Adjusting remote volume without a controller!");
+            }
+        } else if (getStreamVolume(sc.streamType) != index) {
+            if (sc.streamType == STREAM_MASTER) {
+                mAudioManager.setMasterVolume(index, flags);
+            } else {
+                mAudioManager.setStreamVolume(sc.streamType, index, flags);
+            }
         }
     }
 
@@ -549,7 +574,7 @@
         if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
             // never disable touch interactions for remote playback, the muting is not tied to
             // the state of the phone.
-            sc.seekbarView.setEnabled(true);
+            sc.seekbarView.setEnabled(!fixedVolume);
         } else if (fixedVolume ||
                         (sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
                         (sConfirmSafeVolumeDialog != null)) {
@@ -677,7 +702,7 @@
         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
     }
 
-    public void postRemoteVolumeChanged(int streamType, int flags) {
+    public void postRemoteVolumeChanged(MediaController controller, int flags) {
         if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
         synchronized (this) {
             if (mStreamControls == null) {
@@ -685,7 +710,7 @@
             }
         }
         removeMessages(MSG_FREE_RESOURCES);
-        obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
+        obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget();
     }
 
     public void postRemoteSliderVisibility(boolean visible) {
@@ -758,7 +783,7 @@
                 if (mActiveStreamType != streamType) {
                     reorderSliders(streamType);
                 }
-                onShowVolumeChanged(streamType, flags);
+                onShowVolumeChanged(streamType, flags, null);
             }
         }
 
@@ -790,7 +815,7 @@
         onVolumeChanged(streamType, flags);
     }
 
-    protected void onShowVolumeChanged(int streamType, int flags) {
+    protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
         int index = getStreamVolume(streamType);
 
         mRingIsSilent = false;
@@ -803,6 +828,7 @@
         // get max volume for progress bar
 
         int max = getStreamMaxVolume(streamType);
+        StreamControl sc = mStreamControls.get(streamType);
 
         switch (streamType) {
 
@@ -865,13 +891,37 @@
             }
 
             case AudioService.STREAM_REMOTE_MUSIC: {
+                if (controller == null && sc != null) {
+                    // If we weren't passed one try using the last one set.
+                    controller = sc.controller;
+                }
+                if (controller == null) {
+                    // We still don't have one, ignore the command.
+                    Log.w(mTag, "sent remote volume change without a controller!");
+                } else {
+                    VolumeInfo vi = controller.getVolumeInfo();
+                    index = vi.getCurrentVolume();
+                    max = vi.getMaxVolume();
+                    if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) {
+                        // if the remote volume is fixed add the flag for the UI
+                        flags |= AudioManager.FLAG_FIXED_VOLUME;
+                    }
+                }
                 if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
                 break;
             }
         }
 
-        StreamControl sc = mStreamControls.get(streamType);
         if (sc != null) {
+            if (streamType == AudioService.STREAM_REMOTE_MUSIC && controller != sc.controller) {
+                if (sc.controller != null) {
+                    sc.controller.removeCallback(mMediaControllerCb);
+                }
+                sc.controller = controller;
+                if (controller != null) {
+                    sc.controller.addCallback(mMediaControllerCb);
+                }
+            }
             if (sc.seekbarView.getMax() != max) {
                 sc.seekbarView.setMax(max);
             }
@@ -949,34 +999,21 @@
         mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
     }
 
-    protected void onRemoteVolumeChanged(int streamType, int flags) {
-        // streamType is the real stream type being affected, but for the UI sliders, we
-        // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
-        // stream type.
-        if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
+    protected void onRemoteVolumeChanged(MediaController controller, int flags) {
+        if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " + flags
+                    + ")");
 
         if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
             synchronized (this) {
                 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
                     reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
                 }
-                onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
+                onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags, controller);
             }
         } else {
             if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
         }
 
-        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
-            removeMessages(MSG_PLAY_SOUND);
-            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
-        }
-
-        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
-            removeMessages(MSG_PLAY_SOUND);
-            removeMessages(MSG_VIBRATE);
-            onStopSounds();
-        }
-
         removeMessages(MSG_FREE_RESOURCES);
         sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
         resetTimeout();
@@ -987,10 +1024,24 @@
         if (isShowing()
                 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
                 && (mStreamControls != null)) {
-            onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
+            onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0, null);
         }
     }
 
+    /**
+     * Clear the current remote stream controller.
+     */
+    private void clearRemoteStreamController() {
+        if (mStreamControls != null) {
+            StreamControl sc = mStreamControls.get(AudioService.STREAM_REMOTE_MUSIC);
+            if (sc != null) {
+                if (sc.controller != null) {
+                    sc.controller.removeCallback(mMediaControllerCb);
+                    sc.controller = null;
+                }
+            }
+        }
+    }
 
     /**
      * Handler for MSG_SLIDER_VISIBILITY_CHANGED
@@ -1137,6 +1188,7 @@
                 if (isShowing()) {
                     if (mDialog != null) {
                         mDialog.dismiss();
+                        clearRemoteStreamController();
                         mActiveStreamType = -1;
                     }
                 }
@@ -1155,7 +1207,7 @@
             }
 
             case MSG_REMOTE_VOLUME_CHANGED: {
-                onRemoteVolumeChanged(msg.arg1, msg.arg2);
+                onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1);
                 break;
             }
 
@@ -1202,9 +1254,7 @@
             final Object tag = seekBar.getTag();
             if (fromUser && tag instanceof StreamControl) {
                 StreamControl sc = (StreamControl) tag;
-                if (getStreamVolume(sc.streamType) != progress) {
-                    setStreamVolume(sc.streamType, progress, 0);
-                }
+                setStreamVolume(sc, progress, 0);
             }
             resetTimeout();
         }
@@ -1215,19 +1265,6 @@
 
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
-            final Object tag = seekBar.getTag();
-            if (tag instanceof StreamControl) {
-                StreamControl sc = (StreamControl) tag;
-                // Because remote volume updates are asynchronous, AudioService
-                // might have received a new remote volume value since the
-                // finger adjusted the slider. So when the progress of the
-                // slider isn't being tracked anymore, adjust the slider to the
-                // last "published" remote volume value, so the UI reflects the
-                // actual volume.
-                if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
-                    seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
-                }
-            }
         }
     };
 
@@ -1257,4 +1294,10 @@
             postZenModeChanged(zen);
         }
     };
+
+    private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
+        public void onVolumeInfoChanged(VolumeInfo info) {
+            onRemoteVolumeUpdateIfShown();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7da90d8..c1f92ff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -5,7 +5,11 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioManager;
+import android.media.IRemoteVolumeController;
 import android.media.IVolumeController;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -42,12 +46,21 @@
 
     private final Handler mHandler = new Handler();
     private AudioManager mAudioManager;
+    private MediaSessionManager mMediaSessionManager;
     private VolumeController mVolumeController;
+    private RemoteVolumeController mRemoteVolumeController;
+
+    private VolumePanel mDialogPanel;
+    private VolumePanel mPanel;
 
     @Override
     public void start() {
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mVolumeController = new VolumeController(mContext);
+        mMediaSessionManager = (MediaSessionManager) mContext
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        initPanel();
+        mVolumeController = new VolumeController();
+        mRemoteVolumeController = new RemoteVolumeController();
         putComponent(VolumeComponent.class, mVolumeController);
         updateController();
         mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
@@ -57,12 +70,32 @@
         if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
             Log.d(TAG, "Registering volume controller");
             mAudioManager.setVolumeController(mVolumeController);
+            mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
         } else {
             Log.d(TAG, "Unregistering volume controller");
             mAudioManager.setVolumeController(null);
+            mMediaSessionManager.setRemoteVolumeController(null);
         }
     }
 
+    private void initPanel() {
+        mPanel = new VolumePanel(mContext, null, new ZenModeControllerImpl(mContext, mHandler));
+        final int delay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
+        mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
+            @Override
+            public void onMoreSettings() {
+                mHandler.removeCallbacks(mStartZenSettings);
+                mHandler.postDelayed(mStartZenSettings, delay);
+            }
+
+            @Override
+            public void onInteraction() {
+                mDialogPanel.resetTimeout();
+            }
+        });
+        mDialogPanel = mPanel;
+    }
+
     private final ContentObserver mObserver = new ContentObserver(mHandler) {
         public void onChange(boolean selfChange, Uri uri) {
             if (SETTING_URI.equals(uri)) {
@@ -71,55 +104,18 @@
         }
     };
 
+    private final Runnable mStartZenSettings = new Runnable() {
+        @Override
+        public void run() {
+            mDialogPanel.postDismiss();
+            final Intent intent = ZenModePanel.ZEN_SETTINGS;
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+        }
+    };
+
     /** For now, simply host an unmodified base volume panel in this process. */
     private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
-        private final VolumePanel mDialogPanel;
-        private VolumePanel mPanel;
-
-        public VolumeController(Context context) {
-            mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler));
-            final int delay = context.getResources().getInteger(R.integer.feedback_start_delay);
-            mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
-                @Override
-                public void onMoreSettings() {
-                    mHandler.removeCallbacks(mStartZenSettings);
-                    mHandler.postDelayed(mStartZenSettings, delay);
-                }
-
-                @Override
-                public void onInteraction() {
-                    mDialogPanel.resetTimeout();
-                }
-            });
-            mDialogPanel = mPanel;
-        }
-
-        private final Runnable mStartZenSettings = new Runnable() {
-            @Override
-            public void run() {
-                mDialogPanel.postDismiss();
-                final Intent intent = ZenModePanel.ZEN_SETTINGS;
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
-            }
-        };
-
-        @Override
-        public void hasNewRemotePlaybackInfo() throws RemoteException {
-            mPanel.postHasNewRemotePlaybackInfo();
-        }
-
-        @Override
-        public void remoteVolumeChanged(int streamType, int flags)
-                throws RemoteException {
-            mPanel.postRemoteVolumeChanged(streamType, flags);
-        }
-
-        @Override
-        public void remoteSliderVisibility(boolean visible)
-                throws RemoteException {
-            mPanel.postRemoteSliderVisibility(visible);
-        }
 
         @Override
         public void displaySafeVolumeWarning(int flags) throws RemoteException {
@@ -163,4 +159,21 @@
             mPanel = panel == null ? mDialogPanel : panel;
         }
     }
+
+    private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
+
+        @Override
+        public void remoteVolumeChanged(ISessionController binder, int flags)
+                throws RemoteException {
+            MediaController controller = MediaController.fromBinder(binder);
+            mPanel.postRemoteVolumeChanged(controller, flags);
+        }
+
+        @Override
+        public void updateRemoteController(ISessionController session) throws RemoteException {
+            mPanel.postRemoteSliderVisibility(session != null);
+            // TODO stash default session in case the slider can be opened other
+            // than by remoteVolumeChanged.
+        }
+    }
 }
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/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 0c16b78..56a8f7c 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -21,6 +21,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
 
 import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
@@ -64,6 +65,8 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
 import android.view.WindowManagerPolicy.WindowManagerFuncs;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
@@ -94,6 +97,7 @@
     private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
     private static final String GLOBAL_ACTION_KEY_USERS = "users";
     private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+    private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
 
     private final Context mContext;
     private final WindowManagerFuncs mWindowManagerFuncs;
@@ -279,6 +283,8 @@
                 addUsersToMenu(mItems);
             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                 mItems.add(getSettingsAction());
+            } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey) && hasTrustAgents()) {
+                mItems.add(getLockdownAction());
             } else {
                 Log.e(TAG, "Invalid global action key " + actionKey);
             }
@@ -317,6 +323,11 @@
         return dialog;
     }
 
+    private boolean hasTrustAgents() {
+        // TODO: Add implementation.
+        return true;
+    }
+
     private final class PowerAction extends SinglePressAction implements LongPressAction {
         private PowerAction() {
             super(com.android.internal.R.drawable.ic_lock_power_off,
@@ -419,6 +430,32 @@
         };
     }
 
+    private Action getLockdownAction() {
+        return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
+                R.string.global_action_lockdown) {
+
+            @Override
+            public void onPress() {
+                new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+                try {
+                    WindowManagerGlobal.getWindowManagerService().lockNow(null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error while trying to lock device.", e);
+                }
+            }
+
+            @Override
+            public boolean showDuringKeyguard() {
+                return true;
+            }
+
+            @Override
+            public boolean showBeforeProvisioning() {
+                return false;
+            }
+        };
+    }
+
     private UserInfo getCurrentUser() {
         try {
             return ActivityManagerNative.getDefault().getCurrentUser();
diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
index b2ecb61..4ad45a8 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
@@ -40,9 +40,6 @@
     private static String TAG = "PhoneFallbackEventHandler";
     private static final boolean DEBUG = false;
 
-    // Use the new sessions APIs
-    private static final boolean USE_SESSIONS = true;
-
     Context mContext;
     View mView;
 
@@ -294,21 +291,7 @@
     }
 
     private void handleMediaKeyEvent(KeyEvent keyEvent) {
-        if (USE_SESSIONS) {
-            MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
-        } else {
-            IAudioService audioService = IAudioService.Stub.asInterface(
-                    ServiceManager.checkService(Context.AUDIO_SERVICE));
-            if (audioService != null) {
-                try {
-                    audioService.dispatchMediaKeyEvent(keyEvent);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
-                }
-            } else {
-                Slog.w(TAG, "Unable to find IAudioService for media key event.");
-            }
-        }
+        MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
     }
 }
 
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..ddfa9c1 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -42,7 +42,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.media.AudioManager;
-import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -51,7 +50,6 @@
 import android.os.FactoryTest;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
@@ -105,7 +103,6 @@
 import com.android.internal.policy.impl.keyguard.KeyguardServiceDelegate;
 import com.android.internal.policy.impl.keyguard.KeyguardServiceDelegate.ShowListener;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.telephony.ITelephony;
 import com.android.internal.widget.PointerLocationView;
 import com.android.server.LocalServices;
 
@@ -144,9 +141,6 @@
     static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true;
     static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false;
 
-    // Whether to use the new Session APIs
-    static final boolean USE_SESSIONS = true;
-
     static final int SHORT_PRESS_POWER_NOTHING = 0;
     static final int SHORT_PRESS_POWER_GO_TO_SLEEP = 1;
     static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2;
@@ -273,10 +267,10 @@
     // The following are only accessed on the mHandler thread.
     boolean mKeyguardDrawComplete;
     boolean mWindowManagerDrawComplete;
-    ArrayList<ScreenOnListener> mScreenOnListeners = new ArrayList<ScreenOnListener>();
-    final IRemoteCallback mWindowManagerDrawCallback = new IRemoteCallback.Stub() {
+    ScreenOnListener mScreenOnListener;
+    final Runnable mWindowManagerDrawCallback = new Runnable() {
         @Override
-        public void sendResult(Bundle data) {
+        public void run() {
             if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display!");
             mHandler.sendEmptyMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE);
         }
@@ -296,6 +290,7 @@
     WindowState mLastInputMethodWindow = null;
     WindowState mLastInputMethodTargetWindow = null;
 
+    boolean mRecentsVisible;
     int mRecentAppsHeldModifiers;
     boolean mLanguageSwitchKeyPressed;
 
@@ -305,6 +300,7 @@
     boolean mSystemReady;
     boolean mSystemBooted;
     boolean mHdmiPlugged;
+    IUiModeManager mUiModeManager;
     int mUiMode;
     int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     int mLidOpenRotation;
@@ -314,6 +310,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;
@@ -431,6 +430,12 @@
      * be done once per window. */
     private WindowState mWinDismissingKeyguard;
 
+    /** The window that is currently showing "over" the keyguard. If there is an app window
+     * belonging to another app on top of this the keyguard shows. If there is a fullscreen
+     * app window under this, still dismiss the keyguard but don't show the app underneath. Show
+     * the wallpaper. */
+    private WindowState mWinShowWhenLocked;
+
     boolean mShowingLockscreen;
     boolean mShowingDream;
     boolean mDreamingLockscreen;
@@ -443,6 +448,7 @@
     boolean mSearchKeyShortcutPending;
     boolean mConsumeSearchKeyUp;
     boolean mAssistKeyLongPressed;
+    boolean mPendingMetaAction;
 
     // support for activating the lock screen while the screen is on
     boolean mAllowLockscreenWhenOn;
@@ -512,6 +518,7 @@
     private static final int MSG_KEYGUARD_DRAWN_TIMEOUT = 6;
     private static final int MSG_WINDOW_MANAGER_DRAWN_COMPLETE = 7;
     private static final int MSG_WAKING_UP = 8;
+    private static final int MSG_DISPATCH_SHOW_RECENTS = 9;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -529,6 +536,9 @@
                 case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK:
                     dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
                     break;
+                case MSG_DISPATCH_SHOW_RECENTS:
+                    showRecentApps(false);
+                    break;
                 case MSG_KEYGUARD_DRAWN_COMPLETE:
                     if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete");
                     mKeyguardDrawComplete = true;
@@ -573,6 +583,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 +616,22 @@
         }
     }
 
+    class MyWakeGestureListener extends WakeGestureListener {
+        MyWakeGestureListener(Context context, Handler handler) {
+            super(context, handler);
+        }
+
+        @Override
+        public void onWakeUp() {
+            synchronized (mLock) {
+                if (shouldEnableWakeGestureLp()) {
+                    performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
+                    mPowerManager.wakeUp(SystemClock.uptimeMillis());
+                }
+            }
+        }
+    }
+
     class MyOrientationListener extends WindowOrientationListener {
         MyOrientationListener(Context context, Handler handler) {
             super(context, handler);
@@ -906,6 +935,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 +1222,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 +1278,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);
@@ -1622,8 +1675,9 @@
     }
 
     @Override
-    public boolean doesForceHide(WindowManager.LayoutParams attrs) {
-        return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+    public boolean isForceHiding(WindowManager.LayoutParams attrs) {
+        return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
+                (isKeyguardHostWindow(attrs) && isKeyguardSecureIncludingHidden());
     }
 
     @Override
@@ -2043,6 +2097,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 +2297,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 +2414,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;
     }
@@ -2517,6 +2590,12 @@
         }
     }
 
+    @Override
+    public void showRecentApps() {
+        mHandler.removeMessages(MSG_DISPATCH_SHOW_RECENTS);
+        mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_RECENTS);
+    }
+
     private void showRecentApps(boolean triggeredFromAltTab) {
         mPreloadedRecentApps = false; // preloading no longer needs to be canceled
         try {
@@ -2573,8 +2652,16 @@
                 ActivityManagerNative.getDefault().stopAppSwitches();
             } catch (RemoteException e) {
             }
-            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
-            startDockOrHome();
+            if (mRecentsVisible) {
+                // Hide Recents and notify it to launch Home
+                awakenDreams();
+                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+                hideRecentApps(false);
+            } else {
+                // Otherwise, just launch Home
+                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+                startDockOrHome();
+            }
         }
     }
 
@@ -2658,6 +2745,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.
@@ -3047,10 +3135,11 @@
 
     /** {@inheritDoc} */
     @Override
-    public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,
-            WindowState attached) {
+    public void layoutWindowLw(WindowState win, WindowState attached) {
         // we've already done the status bar
-        if ((win == mStatusBar && !doesForceHide(attrs)) || win == mNavigationBar) {
+        final WindowManager.LayoutParams attrs = win.getAttrs();
+        if ((win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) == 0) ||
+                win == mNavigationBar) {
             return;
         }
         final boolean isDefaultDisplay = win.isDefaultDisplay();
@@ -3531,12 +3620,12 @@
         mDismissKeyguard = DISMISS_KEYGUARD_NONE;
         mShowingLockscreen = false;
         mShowingDream = false;
+        mWinShowWhenLocked = null;
     }
 
     /** {@inheritDoc} */
     @Override
-    public void applyPostLayoutPolicyLw(WindowState win,
-                                WindowManager.LayoutParams attrs) {
+    public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs) {
         if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw="
                 + win.isVisibleOrBehindKeyguardLw());
         final int fl = PolicyControl.getWindowFlags(win, attrs);
@@ -3574,9 +3663,17 @@
 
             final boolean showWhenLocked = (fl & FLAG_SHOW_WHEN_LOCKED) != 0;
             final boolean dismissKeyguard = (fl & FLAG_DISMISS_KEYGUARD) != 0;
+            final boolean secureKeyguard = isKeyguardSecure();
             if (appWindow) {
-                if (showWhenLocked || (dismissKeyguard && !isKeyguardSecure())) {
+                if (showWhenLocked || (dismissKeyguard && !secureKeyguard)) {
+                    // Remove any previous windows with the same appToken.
                     mAppsToBeHidden.remove(win.getAppToken());
+                    if (mAppsToBeHidden.isEmpty() && showWhenLocked &&
+                            isKeyguardSecureIncludingHidden()) {
+                        mWinShowWhenLocked = win;
+                        mHideLockScreen = true;
+                        mForceStatusBarFromKeyguard = false;
+                    }
                 } else {
                     mAppsToBeHidden.add(win.getAppToken());
                 }
@@ -3598,13 +3695,18 @@
                             mDismissKeyguard = mWinDismissingKeyguard == win ?
                                     DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START;
                             mWinDismissingKeyguard = win;
-                            mForceStatusBarFromKeyguard = mShowingLockscreen && isKeyguardSecure();
+                            mForceStatusBarFromKeyguard = mShowingLockscreen && secureKeyguard;
                         }
                     }
                     if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
                         mAllowLockscreenWhenOn = true;
                     }
                 }
+
+                if (mWinShowWhenLocked != null &&
+                        mWinShowWhenLocked.getAppToken() != win.getAppToken()) {
+                    win.hideLw(false);
+                }
             }
         }
     }
@@ -3612,6 +3714,16 @@
     /** {@inheritDoc} */
     @Override
     public int finishPostLayoutPolicyLw() {
+        if (mWinShowWhenLocked != null &&
+                mWinShowWhenLocked != mTopFullscreenOpaqueWindowState) {
+            // A dialog is dismissing the keyguard. Put the wallpaper behind it and hide the
+            // fullscreen window.
+            // TODO: Make sure FLAG_SHOW_WALLPAPER is restored when dialog is dismissed. Or not.
+            mWinShowWhenLocked.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
+            mTopFullscreenOpaqueWindowState.hideLw(false);
+            mTopFullscreenOpaqueWindowState = mWinShowWhenLocked;
+        }
+
         int changes = 0;
         boolean topIsFullscreen = false;
 
@@ -3870,6 +3982,7 @@
      *    controlled by this device, or through remote submix).
      */
     boolean isMusicActive() {
+
         final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
         if (am == null) {
             Log.w(TAG, "isMusicActive: couldn't get AudioManager reference");
@@ -3878,43 +3991,6 @@
         return am.isLocalOrRemoteMusicActive();
     }
 
-    /**
-     * Tell the audio service to adjust the volume appropriate to the event.
-     * @param keycode
-     */
-    void handleVolumeKey(int stream, int keycode) {
-        IAudioService audioService = getAudioService();
-        if (audioService == null) {
-            return;
-        }
-        try {
-            // when audio is playing locally, we shouldn't have to hold a wake lock
-            // during the call, but we do it as a precaution for the rare possibility
-            // that the music stops right before we call this.
-            // Otherwise we might also be in a remote playback case.
-            // TODO: Actually handle MUTE.
-            mBroadcastWakeLock.acquire();
-            if (stream == AudioSystem.STREAM_MUSIC) {
-                audioService.adjustLocalOrRemoteStreamVolume(stream,
-                        keycode == KeyEvent.KEYCODE_VOLUME_UP
-                                ? AudioManager.ADJUST_RAISE
-                                : AudioManager.ADJUST_LOWER,
-                        mContext.getOpPackageName());
-            } else {
-                audioService.adjustStreamVolume(stream,
-                        keycode == KeyEvent.KEYCODE_VOLUME_UP
-                                ? AudioManager.ADJUST_RAISE
-                                : AudioManager.ADJUST_LOWER,
-                        0,
-                        mContext.getOpPackageName());
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "IAudioService.adjust*StreamVolume() threw RemoteException " + e);
-        } finally {
-            mBroadcastWakeLock.release();
-        }
-    }
-
     final Object mScreenshotLock = new Object();
     ServiceConnection mScreenshotConnection = null;
 
@@ -4014,11 +4090,6 @@
                     + " policyFlags=" + Integer.toHexString(policyFlags));
         }
 
-        if (down && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
-                && event.getRepeatCount() == 0) {
-            performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
-        }
-
         // Basic policy based on interactive state.
         int result;
         boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
@@ -4038,9 +4109,16 @@
         // If the key would be handled globally, just return the result, don't worry about special
         // key processing.
         if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) {
+            if (isWakeKey) {
+                mPowerManager.wakeUp(event.getEventTime());
+            }
             return result;
         }
 
+        boolean useHapticFeedback = down
+                && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
+                && event.getRepeatCount() == 0;
+
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -4098,16 +4176,20 @@
                         if (telephonyManager.isOffhook()
                                 && (result & ACTION_PASS_TO_USER) == 0) {
                             // If we are in call but we decided not to pass the key to
-                            // the application, handle the volume change here.
-                            handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode);
+                            // the application, just pass it to the session service.
+
+                            MediaSessionLegacyHelper.getHelper(mContext)
+                                    .sendMediaButtonEvent(event, true);
                             break;
                         }
                     }
 
-                    if (isMusicActive() && (result & ACTION_PASS_TO_USER) == 0) {
-                        // If music is playing but we decided not to pass the key to the
-                        // application, handle the volume change here.
-                        handleVolumeKey(AudioManager.STREAM_MUSIC, keyCode);
+                    if ((result & ACTION_PASS_TO_USER) == 0) {
+                        // If we aren't passing to the user and no one else
+                        // handled it send it to the session manager to figure
+                        // out.
+                        MediaSessionLegacyHelper.getHelper(mContext)
+                                .sendMediaButtonEvent(event, true);
                         break;
                     }
                 }
@@ -4187,6 +4269,9 @@
 
             case KeyEvent.KEYCODE_SLEEP: {
                 result &= ~ACTION_PASS_TO_USER;
+                if (!mPowerManager.isInteractive()) {
+                    useHapticFeedback = false; // suppress feedback if already non-interactive
+                }
                 mPowerManager.goToSleep(event.getEventTime());
                 isWakeKey = false;
                 break;
@@ -4253,6 +4338,10 @@
             }
         }
 
+        if (useHapticFeedback) {
+            performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
+        }
+
         if (isWakeKey) {
             mPowerManager.wakeUp(event.getEventTime());
         }
@@ -4349,18 +4438,7 @@
 
     void dispatchMediaKeyWithWakeLockToAudioService(KeyEvent event) {
         if (ActivityManagerNative.isSystemReady()) {
-            if (USE_SESSIONS) {
-                MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(event, true);
-            } else {
-                IAudioService audioService = getAudioService();
-                if (audioService != null) {
-                    try {
-                        audioService.dispatchMediaKeyEventUnderWakelock(event);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
-                    }
-                }
-            }
+            MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(event, true);
         }
     }
 
@@ -4461,6 +4539,7 @@
             mKeyguardDelegate.onScreenTurnedOff(why);
         }
         synchronized (mLock) {
+            updateWakeGestureListenerLp();
             updateOrientationListenerLp();
             updateLockScreenTimeout();
         }
@@ -4477,11 +4556,12 @@
     // Called on the mHandler thread.
     private void handleWakingUp(final ScreenOnListener screenOnListener) {
         if (screenOnListener != null) {
-            mScreenOnListeners.add(screenOnListener);
+            mScreenOnListener = screenOnListener;
         }
 
         synchronized (mLock) {
             mScreenOnEarly = true;
+            updateWakeGestureListenerLp();
             updateOrientationListenerLp();
             updateLockScreenTimeout();
         }
@@ -4508,8 +4588,11 @@
             return;
         }
 
+        ScreenOnListener screenOnListener;
         synchronized (mLock) {
             mScreenOnFully = true;
+            screenOnListener = mScreenOnListener;
+            mScreenOnListener = null;
         }
 
         try {
@@ -4517,8 +4600,8 @@
         } catch (RemoteException unhandled) {
         }
 
-        for (int i = mScreenOnListeners.size() - 1; i >=0; --i) {
-            mScreenOnListeners.remove(i).onScreenOn();
+        if (screenOnListener != null) {
+            screenOnListener.onScreenOn();
         }
 
         setKeyguardDrawn();
@@ -4565,6 +4648,11 @@
         return mKeyguardDelegate.isSecure();
     }
 
+    // Returns true if keyguard is currently locked whether or not it is currently hidden.
+    private boolean isKeyguardSecureIncludingHidden() {
+        return mKeyguardDelegate.isSecure() && mKeyguardDelegate.isShowing();
+    }
+
     /** {@inheritDoc} */
     public boolean inKeyguardRestrictedKeyInputMode() {
         if (mKeyguardDelegate == null) return false;
@@ -4874,6 +4962,7 @@
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext, null);
         mKeyguardDelegate.onSystemReady();
 
+        updateUiMode();
         synchronized (mLock) {
             updateOrientationListenerLp();
             mSystemReady = true;
@@ -5056,6 +5145,21 @@
                     PowerManager.GO_TO_SLEEP_REASON_USER,
                     PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
         }
+
+        synchronized (mLock) {
+            updateWakeGestureListenerLp();
+        }
+    }
+
+    void updateUiMode() {
+        if (mUiModeManager == null) {
+            mUiModeManager = IUiModeManager.Stub.asInterface(
+                    ServiceManager.getService(Context.UI_MODE_SERVICE));
+        }
+        try {
+            mUiMode = mUiModeManager.getCurrentModeType();
+        } catch (RemoteException e) {
+        }
     }
 
     void updateRotation(boolean alwaysSendConfiguration) {
@@ -5103,6 +5207,12 @@
             if (ENABLE_DESK_DOCK_HOME_CAPTURE) {
                 intent = mDeskDockIntent;
             }
+        } else if (mUiMode == Configuration.UI_MODE_TYPE_WATCH
+                && (mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+                        || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK
+                        || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK)) {
+            // Always launch dock home from home when watch is docked, if it exists.
+            intent = mDeskDockIntent;
         }
 
         if (intent == null) {
@@ -5516,6 +5626,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 +5770,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..704da33 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,21 @@
      */
     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);
+
+            if (mSensorEventListener != null) {
+                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 +360,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 +390,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 +413,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 +523,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 +582,9 @@
                         }
                     }
                 }
+                mFlat = isFlat;
+                mSwinging = isSwinging;
+                mAccelerating = isAccelerating;
 
                 // Determine new proposed rotation.
                 oldProposedRotation = mProposedRotation;
@@ -543,6 +602,7 @@
                             + ", isAccelerating=" + isAccelerating
                             + ", isFlat=" + isFlat
                             + ", isSwinging=" + isSwinging
+                            + ", isOverhead=" + mOverhead
                             + ", timeUntilSettledMS=" + remainingMS(now,
                                     mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
                             + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
@@ -660,8 +720,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 +790,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/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 2191b54..d9588e8 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -770,10 +770,11 @@
         mRS.validate();
         int eSize = mType.mElement.getBytesSize();
         final byte[] data = fp.getData();
+        int data_length = fp.getPos();
 
-        int count = data.length / eSize;
-        if ((eSize * count) != data.length) {
-            throw new RSIllegalArgumentException("Field packer length " + data.length +
+        int count = data_length / eSize;
+        if ((eSize * count) != data_length) {
+            throw new RSIllegalArgumentException("Field packer length " + data_length +
                                                " not divisible by element size " + eSize + ".");
         }
         copy1DRangeFromUnchecked(xoff, count, data);
@@ -797,16 +798,17 @@
         }
 
         final byte[] data = fp.getData();
+        int data_length = fp.getPos();
         int eSize = mType.mElement.mElements[component_number].getBytesSize();
         eSize *= mType.mElement.mArraySizes[component_number];
 
-        if (data.length != eSize) {
-            throw new RSIllegalArgumentException("Field packer sizelength " + data.length +
+        if (data_length != eSize) {
+            throw new RSIllegalArgumentException("Field packer sizelength " + data_length +
                                                " does not match component size " + eSize + ".");
         }
 
         mRS.nAllocationElementData1D(getIDSafe(), xoff, mSelectedLOD,
-                                     component_number, data, data.length);
+                                     component_number, data, data_length);
     }
 
     private void data1DChecks(int off, int count, int len, int dataSize) {
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index 55b671d..c6b5b0d 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -140,17 +140,17 @@
         MATRIX_3X3 (17, 36),
         MATRIX_2X2 (18, 16),
 
-        RS_ELEMENT (1000, 4),
-        RS_TYPE (1001, 4),
-        RS_ALLOCATION (1002, 4),
-        RS_SAMPLER (1003, 4),
-        RS_SCRIPT (1004, 4),
-        RS_MESH (1005, 4),
-        RS_PROGRAM_FRAGMENT (1006, 4),
-        RS_PROGRAM_VERTEX (1007, 4),
-        RS_PROGRAM_RASTER (1008, 4),
-        RS_PROGRAM_STORE (1009, 4),
-        RS_FONT (1010, 4);
+        RS_ELEMENT (1000),
+        RS_TYPE (1001),
+        RS_ALLOCATION (1002),
+        RS_SAMPLER (1003),
+        RS_SCRIPT (1004),
+        RS_MESH (1005),
+        RS_PROGRAM_FRAGMENT (1006),
+        RS_PROGRAM_VERTEX (1007),
+        RS_PROGRAM_RASTER (1008),
+        RS_PROGRAM_STORE (1009),
+        RS_FONT (1010);
 
         int mID;
         int mSize;
@@ -158,6 +158,14 @@
             mID = id;
             mSize = size;
         }
+
+        DataType(int id) {
+            mID = id;
+            mSize = 4;
+            if (RenderScript.sPointerSize == 8) {
+                mSize = 32;
+            }
+        }
     }
 
     /**
diff --git a/rs/java/android/renderscript/FieldPacker.java b/rs/java/android/renderscript/FieldPacker.java
index 723ab24..20b07e7 100644
--- a/rs/java/android/renderscript/FieldPacker.java
+++ b/rs/java/android/renderscript/FieldPacker.java
@@ -36,10 +36,15 @@
     }
 
     public FieldPacker(byte[] data) {
-        mPos = 0;
+        // Advance mPos to the end of the buffer, since we are copying in the
+        // full data input.
+        mPos = data.length;
         mLen = data.length;
         mData = data;
         mAlignment = new BitSet();
+        // TODO: We should either have an actual FieldPacker copy constructor
+        // or drop support for computing alignment like this. As it stands,
+        // subAlign() can never work correctly for copied FieldPacker objects.
     }
 
     public void align(int v) {
@@ -75,7 +80,7 @@
         mPos = 0;
     }
     public void reset(int i) {
-        if ((i < 0) || (i >= mLen)) {
+        if ((i < 0) || (i > mLen)) {
             throw new RSIllegalArgumentException("out of range argument: " + i);
         }
         mPos = i;
@@ -231,10 +236,24 @@
 
     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));
+                addI64(0);
+                addI64(0);
+                addI64(0);
+            }
+            else {
+                addI32((int)obj.getID(null));
+            }
         } else {
-            addI32(0);
+            if (RenderScript.sPointerSize == 8) {
+                addI64(0);
+                addI64(0);
+                addI64(0);
+                addI64(0);
+            } else {
+                addI32(0);
+            }
         }
     }
 
@@ -591,6 +610,15 @@
         return mData;
     }
 
+    /**
+     * Get the actual length used for the FieldPacker.
+     *
+     * @hide
+     */
+    public int getPos() {
+        return mPos;
+    }
+
     private final byte mData[];
     private int mPos;
     private int mLen;
diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java
index b22aeb7..4318b9d 100644
--- a/rs/java/android/renderscript/Font.java
+++ b/rs/java/android/renderscript/Font.java
@@ -111,10 +111,10 @@
 
         FontFamily serifFamily = new FontFamily();
         serifFamily.mNames = sSerifNames;
-        serifFamily.mNormalFileName = "DroidSerif-Regular.ttf";
-        serifFamily.mBoldFileName = "DroidSerif-Bold.ttf";
-        serifFamily.mItalicFileName = "DroidSerif-Italic.ttf";
-        serifFamily.mBoldItalicFileName = "DroidSerif-BoldItalic.ttf";
+        serifFamily.mNormalFileName = "NotoSerif-Regular.ttf";
+        serifFamily.mBoldFileName = "NotoSerif-Bold.ttf";
+        serifFamily.mItalicFileName = "NotoSerif-Italic.ttf";
+        serifFamily.mBoldItalicFileName = "NotoSerif-BoldItalic.ttf";
         addFamilyToMap(serifFamily);
 
         FontFamily monoFamily = new FontFamily();
diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
index 5173af2..45840ae 100644
--- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -245,6 +245,9 @@
             for(int i = 0; i < 16; i ++) {
                 mIOBuffer.addF32(m.mMat[i]);
             }
+            // Reset the buffer back to the end, since we want to flush all of
+            // the contents back (and not just what we wrote now).
+            mIOBuffer.reset(mIOBuffer.getData().length);
             mAlloc.setFromFieldPacker(0, mIOBuffer);
         }
 
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..64d21e4 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -44,7 +44,6 @@
      * @param id
      * @param rs
      *
-     * @hide
      */
     protected ScriptC(long id, RenderScript rs) {
         super(id, rs);
@@ -67,6 +66,25 @@
     }
 
     /**
+     * Only intended for use by the generated derived classes.
+     *
+     * @param rs
+     */
+    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 +131,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/java/android/renderscript/ScriptIntrinsic.java b/rs/java/android/renderscript/ScriptIntrinsic.java
index 8719e017..4edce84 100644
--- a/rs/java/android/renderscript/ScriptIntrinsic.java
+++ b/rs/java/android/renderscript/ScriptIntrinsic.java
@@ -27,5 +27,8 @@
 public abstract class ScriptIntrinsic extends Script {
     ScriptIntrinsic(long id, RenderScript rs) {
         super(id, rs);
+        if (id == 0) {
+            throw new RSRuntimeException("Loading of ScriptIntrinsic failed.");
+        }
     }
 }
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 18a2e31..fb3b117 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1051,7 +1051,7 @@
     jint dimsLen = _env->GetArrayLength(dims) * sizeof(int);
     jint *dimsPtr = _env->GetIntArrayElements(dims, NULL);
     rsScriptSetVarVE((RsContext)con, (RsScript)script, slot, ptr, len, (RsElement)elem,
-                     (const size_t*) dimsPtr, dimsLen);
+                     (const uint32_t*) dimsPtr, dimsLen);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
     _env->ReleaseIntArrayElements(dims, dimsPtr, JNI_ABORT);
 }
@@ -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/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9e893da..9f080ca 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -219,6 +219,7 @@
         // Wait for a down key event to start processing.
         if (!mKeyEventSequenceStarted) {
             if (event.getAction() != KeyEvent.ACTION_DOWN) {
+                super.onInputEvent(event, policyFlags);
                 return;
             }
             mKeyEventSequenceStarted = true;
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..3b1e88a 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -26,7 +26,9 @@
 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.RestoreDescription;
 import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -82,7 +84,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;
@@ -201,7 +202,7 @@
     private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
     private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
     private static final int MSG_RUN_BACKUP = 1;
-    private static final int MSG_RUN_FULL_BACKUP = 2;
+    private static final int MSG_RUN_ADB_BACKUP = 2;
     private static final int MSG_RUN_RESTORE = 3;
     private static final int MSG_RUN_CLEAR = 4;
     private static final int MSG_RUN_INITIALIZE = 5;
@@ -209,10 +210,11 @@
     private static final int MSG_TIMEOUT = 7;
     private static final int MSG_RESTORE_TIMEOUT = 8;
     private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
-    private static final int MSG_RUN_FULL_RESTORE = 10;
+    private static final int MSG_RUN_ADB_RESTORE = 10;
     private static final int MSG_RETRY_INIT = 11;
     private static final int MSG_RETRY_CLEAR = 12;
     private static final int MSG_WIDGET_BROADCAST = 13;
+    private static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
 
     // backup task state machine tick
     static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -448,11 +450,12 @@
         public boolean doWidgets;
         public boolean allApps;
         public boolean includeSystem;
+        public boolean doCompress;
         public String[] packages;
 
         FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
                 boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
-                String[] pkgList) {
+                boolean compress, String[] pkgList) {
             fd = output;
             includeApks = saveApks;
             includeObbs = saveObbs;
@@ -460,6 +463,7 @@
             doWidgets = alsoWidgets;
             allApps = doAllApps;
             includeSystem = doSystem;
+            doCompress = compress;
             packages = pkgList;
         }
     }
@@ -646,17 +650,25 @@
                 break;
             }
 
-            case MSG_RUN_FULL_BACKUP:
+            case MSG_RUN_ADB_BACKUP:
             {
                 // TODO: refactor full backup to be a looper-based state machine
                 // similar to normal backup/restore.
                 FullBackupParams params = (FullBackupParams)msg.obj;
-                PerformFullBackupTask task = new PerformFullBackupTask(params.fd,
+                PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd,
                         params.observer, params.includeApks, params.includeObbs,
                         params.includeShared, params.doWidgets,
                         params.curPassword, params.encryptPassword,
-                        params.allApps, params.includeSystem, params.packages, params.latch);
-                (new Thread(task)).start();
+                        params.allApps, params.includeSystem, params.doCompress,
+                        params.packages, params.latch);
+                (new Thread(task, "adb-backup")).start();
+                break;
+            }
+
+            case MSG_RUN_FULL_TRANSPORT_BACKUP:
+            {
+                PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
+                (new Thread(task, "transport-backup")).start();
                 break;
             }
 
@@ -673,7 +685,7 @@
                 break;
             }
 
-            case MSG_RUN_FULL_RESTORE:
+            case MSG_RUN_ADB_RESTORE:
             {
                 // TODO: refactor full restore to be a looper-based state machine
                 // similar to normal backup/restore.
@@ -681,7 +693,7 @@
                 PerformFullRestoreTask task = new PerformFullRestoreTask(params.fd,
                         params.curPassword, params.encryptPassword,
                         params.observer, params.latch);
-                (new Thread(task)).start();
+                (new Thread(task, "adb-restore")).start();
                 break;
             }
 
@@ -2098,7 +2110,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 +2133,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 +2153,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 +2161,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 +2170,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 +2188,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 +2248,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 +2309,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 +2326,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 +2407,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 +2415,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 +2496,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 +2513,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 +2526,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 +2537,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 +2545,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 {
@@ -2618,13 +2630,6 @@
 
     // ----- Full backup/restore to a file/socket -----
 
-    abstract class ObbServiceClient {
-        public IObbBackupService mObbService;
-        public void setObbBinder(IObbBackupService binder) {
-            mObbService = binder;
-        }
-    }
-
     class FullBackupObbConnection implements ServiceConnection {
         volatile IObbBackupService mService;
 
@@ -2736,24 +2741,15 @@
         }
     }
 
-    class PerformFullBackupTask extends ObbServiceClient implements Runnable {
-        ParcelFileDescriptor mOutputFile;
-        DeflaterOutputStream mDeflater;
+    // Core logic for performing one package's full backup, gathering the tarball from the
+    // application and emitting it to the designated OutputStream.
+    class FullBackupEngine {
+        OutputStream mOutput;
         IFullBackupRestoreObserver mObserver;
-        boolean mIncludeApks;
-        boolean mIncludeObbs;
-        boolean mIncludeShared;
-        boolean mDoWidgets;
-        boolean mAllApps;
-        final boolean mIncludeSystem;
-        ArrayList<String> mPackages;
-        String mCurrentPassword;
-        String mEncryptPassword;
-        AtomicBoolean mLatchObject;
         File mFilesDir;
         File mManifestFile;
         File mMetadataFile;
-        
+        boolean mIncludeApks;
 
         class FullBackupRunner implements Runnable {
             PackageInfo mPackage;
@@ -2823,326 +2819,17 @@
             }
         }
 
-        PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, 
-                boolean includeApks, boolean includeObbs, boolean includeShared,
-                boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
-                boolean doSystem, String[] packages, AtomicBoolean latch) {
-            mOutputFile = fd;
-            mObserver = observer;
-            mIncludeApks = includeApks;
-            mIncludeObbs = includeObbs;
-            mIncludeShared = includeShared;
-            mDoWidgets = doWidgets;
-            mAllApps = doAllApps;
-            mIncludeSystem = doSystem;
-            mPackages = (packages == null)
-                    ? new ArrayList<String>()
-                    : new ArrayList<String>(Arrays.asList(packages));
-            mCurrentPassword = curPassword;
-            // when backing up, if there is a current backup password, we require that
-            // the user use a nonempty encryption password as well.  if one is supplied
-            // in the UI we use that, but if the UI was left empty we fall back to the
-            // current backup password (which was supplied by the user as well).
-            if (encryptPassword == null || "".equals(encryptPassword)) {
-                mEncryptPassword = curPassword;
-            } else {
-                mEncryptPassword = encryptPassword;
-            }
-            mLatchObject = latch;
-
+        FullBackupEngine(OutputStream output, String packageName, boolean alsoApks) {
+            mOutput = output;
+            mIncludeApks = alsoApks;
             mFilesDir = new File("/data/system");
             mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
             mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
         }
 
-        void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
-            for (String pkgName : pkgNames) {
-                if (!set.containsKey(pkgName)) {
-                    try {
-                        PackageInfo info = mPackageManager.getPackageInfo(pkgName,
-                                PackageManager.GET_SIGNATURES);
-                        set.put(pkgName, info);
-                    } catch (NameNotFoundException e) {
-                        Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
-                    }
-                }
-            }
-        }
 
-        @Override
-        public void run() {
-            Slog.i(TAG, "--- Performing full-dataset backup ---");
-
-            TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
-            FullBackupObbConnection obbConnection = new FullBackupObbConnection();
-            obbConnection.establish();  // we'll want this later
-
-            sendStartBackup();
-
-            // doAllApps supersedes the package set if any
-            if (mAllApps) {
-                List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
-                        PackageManager.GET_SIGNATURES);
-                for (int i = 0; i < allPackages.size(); i++) {
-                    PackageInfo pkg = allPackages.get(i);
-                    // Exclude system apps if we've been asked to do so
-                    if (mIncludeSystem == true
-                            || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
-                        packagesToBackup.put(pkg.packageName, pkg);
-                    }
-                }
-            }
-
-            // If we're doing widget state as well, ensure that we have all the involved
-            // host & provider packages in the set
-            if (mDoWidgets) {
-                List<String> pkgs =
-                        AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_OWNER);
-                if (pkgs != null) {
-                    if (MORE_DEBUG) {
-                        Slog.i(TAG, "Adding widget participants to backup set:");
-                        StringBuilder sb = new StringBuilder(128);
-                        sb.append("   ");
-                        for (String s : pkgs) {
-                            sb.append(' ');
-                            sb.append(s);
-                        }
-                        Slog.i(TAG, sb.toString());
-                    }
-                    addPackagesToSet(packagesToBackup, pkgs);
-                }
-            }
-
-            // Now process the command line argument packages, if any. Note that explicitly-
-            // named system-partition packages will be included even if includeSystem was
-            // set to false.
-            if (mPackages != null) {
-                addPackagesToSet(packagesToBackup, mPackages);
-            }
-
-            // Now we cull any inapplicable / inappropriate packages from the set
-            Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
-            while (iter.hasNext()) {
-                PackageInfo pkg = iter.next().getValue();
-                if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
-                        || pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
-                    // Cull any packages that have indicated that backups are not permitted, as well
-                    // as any explicit mention of the 'special' shared-storage agent package (we
-                    // handle that one at the end).
-                    iter.remove();
-                } else if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
-                        && (pkg.applicationInfo.backupAgentName == null)) {
-                    // Cull any packages that run as system-domain uids but do not define their
-                    // own backup agents
-                    if (MORE_DEBUG) {
-                        Slog.i(TAG, "... ignoring non-agent system package " + pkg.packageName);
-                    }
-                    iter.remove();
-                }
-            }
-
-            // flatten the set of packages now so we can explicitly control the ordering
-            ArrayList<PackageInfo> backupQueue =
-                    new ArrayList<PackageInfo>(packagesToBackup.values());
-
-            FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
-            OutputStream out = null;
-
-            PackageInfo pkg = null;
-            try {
-                boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0);
-                boolean compressing = COMPRESS_FULL_BACKUPS;
-                OutputStream finalOutput = ofstream;
-
-                // Verify that the given password matches the currently-active
-                // backup password, if any
-                if (!backupPasswordMatches(mCurrentPassword)) {
-                    if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
-                    return;
-                }
-
-                // Write the global file header.  All strings are UTF-8 encoded; lines end
-                // with a '\n' byte.  Actual backup data begins immediately following the
-                // final '\n'.
-                //
-                // line 1: "ANDROID BACKUP"
-                // line 2: backup file format version, currently "2"
-                // line 3: compressed?  "0" if not compressed, "1" if compressed.
-                // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
-                //
-                // When line 4 is not "none", then additional header data follows:
-                //
-                // line 5: user password salt [hex]
-                // line 6: master key checksum salt [hex]
-                // line 7: number of PBKDF2 rounds to use (same for user & master) [decimal]
-                // line 8: IV of the user key [hex]
-                // line 9: master key blob [hex]
-                //     IV of the master key, master key itself, master key checksum hash
-                //
-                // The master key checksum is the master key plus its checksum salt, run through
-                // 10k rounds of PBKDF2.  This is used to verify that the user has supplied the
-                // correct password for decrypting the archive:  the master key decrypted from
-                // the archive using the user-supplied password is also run through PBKDF2 in
-                // this way, and if the result does not match the checksum as stored in the
-                // archive, then we know that the user-supplied password does not match the
-                // archive's.
-                StringBuilder headerbuf = new StringBuilder(1024);
-
-                headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
-                headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n
-                headerbuf.append(compressing ? "\n1\n" : "\n0\n");
-
-                try {
-                    // Set up the encryption stage if appropriate, and emit the correct header
-                    if (encrypting) {
-                        finalOutput = emitAesBackupHeader(headerbuf, finalOutput);
-                    } else {
-                        headerbuf.append("none\n");
-                    }
-
-                    byte[] header = headerbuf.toString().getBytes("UTF-8");
-                    ofstream.write(header);
-
-                    // Set up the compression stage feeding into the encryption stage (if any)
-                    if (compressing) {
-                        Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
-                        finalOutput = new DeflaterOutputStream(finalOutput, deflater, true);
-                    }
-
-                    out = finalOutput;
-                } catch (Exception e) {
-                    // Should never happen!
-                    Slog.e(TAG, "Unable to emit archive header", e);
-                    return;
-                }
-
-                // Shared storage if requested
-                if (mIncludeShared) {
-                    try {
-                        pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
-                        backupQueue.add(pkg);
-                    } catch (NameNotFoundException e) {
-                        Slog.e(TAG, "Unable to find shared-storage backup handler");
-                    }
-                }
-
-                // Now back up the app data via the agent mechanism
-                int N = backupQueue.size();
-                for (int i = 0; i < N; i++) {
-                    pkg = backupQueue.get(i);
-                    backupOnePackage(pkg, out);
-
-                    // after the app's agent runs to handle its private filesystem
-                    // contents, back up any OBB content it has on its behalf.
-                    if (mIncludeObbs) {
-                        boolean obbOkay = obbConnection.backupObbs(pkg, out);
-                        if (!obbOkay) {
-                            throw new RuntimeException("Failure writing OBB stack for " + pkg);
-                        }
-                    }
-                }
-
-                // Done!
-                finalizeBackup(out);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "App died during full backup");
-            } catch (Exception e) {
-                Slog.e(TAG, "Internal exception during full backup", e);
-            } finally {
-                tearDown(pkg);
-                try {
-                    if (out != null) out.close();
-                    mOutputFile.close();
-                } catch (IOException e) {
-                    /* nothing we can do about this */
-                }
-                synchronized (mCurrentOpLock) {
-                    mCurrentOperations.clear();
-                }
-                synchronized (mLatchObject) {
-                    mLatchObject.set(true);
-                    mLatchObject.notifyAll();
-                }
-                sendEndBackup();
-                obbConnection.tearDown();
-                if (DEBUG) Slog.d(TAG, "Full backup pass complete.");
-                mWakelock.release();
-            }
-        }
-
-        private OutputStream emitAesBackupHeader(StringBuilder headerbuf,
-                OutputStream ofstream) throws Exception {
-            // User key will be used to encrypt the master key.
-            byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE);
-            SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt,
-                    PBKDF2_HASH_ROUNDS);
-
-            // the master key is random for each backup
-            byte[] masterPw = new byte[256 / 8];
-            mRng.nextBytes(masterPw);
-            byte[] checksumSalt = randomBytes(PBKDF2_SALT_SIZE);
-
-            // primary encryption of the datastream with the random key
-            Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
-            SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES");
-            c.init(Cipher.ENCRYPT_MODE, masterKeySpec);
-            OutputStream finalOutput = new CipherOutputStream(ofstream, c);
-
-            // line 4: name of encryption algorithm
-            headerbuf.append(ENCRYPTION_ALGORITHM_NAME);
-            headerbuf.append('\n');
-            // line 5: user password salt [hex]
-            headerbuf.append(byteArrayToHex(newUserSalt));
-            headerbuf.append('\n');
-            // line 6: master key checksum salt [hex]
-            headerbuf.append(byteArrayToHex(checksumSalt));
-            headerbuf.append('\n');
-            // line 7: number of PBKDF2 rounds used [decimal]
-            headerbuf.append(PBKDF2_HASH_ROUNDS);
-            headerbuf.append('\n');
-
-            // line 8: IV of the user key [hex]
-            Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding");
-            mkC.init(Cipher.ENCRYPT_MODE, userKey);
-
-            byte[] IV = mkC.getIV();
-            headerbuf.append(byteArrayToHex(IV));
-            headerbuf.append('\n');
-
-            // line 9: master IV + key blob, encrypted by the user key [hex].  Blob format:
-            //    [byte] IV length = Niv
-            //    [array of Niv bytes] IV itself
-            //    [byte] master key length = Nmk
-            //    [array of Nmk bytes] master key itself
-            //    [byte] MK checksum hash length = Nck
-            //    [array of Nck bytes] master key checksum hash
-            //
-            // The checksum is the (master key + checksum salt), run through the
-            // stated number of PBKDF2 rounds
-            IV = c.getIV();
-            byte[] mk = masterKeySpec.getEncoded();
-            byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
-                    checksumSalt, PBKDF2_HASH_ROUNDS);
-
-            ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
-                    + checksum.length + 3);
-            DataOutputStream mkOut = new DataOutputStream(blob);
-            mkOut.writeByte(IV.length);
-            mkOut.write(IV);
-            mkOut.writeByte(mk.length);
-            mkOut.write(mk);
-            mkOut.writeByte(checksum.length);
-            mkOut.write(checksum);
-            mkOut.flush();
-            byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
-            headerbuf.append(byteArrayToHex(encryptedMk));
-            headerbuf.append('\n');
-
-            return finalOutput;
-        }
-
-        private void backupOnePackage(PackageInfo pkg, OutputStream out)
-                throws RemoteException {
+        public int backupOnePackage(PackageInfo pkg) throws RemoteException {
+            int result = BackupTransport.TRANSPORT_OK;
             Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
 
             IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
@@ -3162,47 +2849,52 @@
 
                     byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
                             UserHandle.USER_OWNER);
-                    sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                     final int token = generateToken();
                     FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
                             token, sendApk, !isSharedStorage, widgetBlob);
                     pipes[1].close();   // the runner has dup'd it
                     pipes[1] = null;
-                    Thread t = new Thread(runner);
+                    Thread t = new Thread(runner, "app-data-runner");
                     t.start();
 
-                    // Now pull data from the app and stuff it into the compressor
+                    // Now pull data from the app and stuff it into the output
                     try {
-                        routeSocketDataToOutput(pipes[0], out);
+                        routeSocketDataToOutput(pipes[0], mOutput);
                     } catch (IOException e) {
                         Slog.i(TAG, "Caught exception reading from agent", e);
+                        result = BackupTransport.AGENT_ERROR;
                     }
 
                     if (!waitUntilOperationComplete(token)) {
                         Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
+                        result = BackupTransport.AGENT_ERROR;
                     } else {
                         if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName);
                     }
 
                 } catch (IOException e) {
                     Slog.e(TAG, "Error backing up " + pkg.packageName, e);
+                    result = BackupTransport.AGENT_ERROR;
                 } finally {
                     try {
                         // flush after every package
-                        out.flush();
+                        mOutput.flush();
                         if (pipes != null) {
                             if (pipes[0] != null) pipes[0].close();
                             if (pipes[1] != null) pipes[1].close();
                         }
                     } catch (IOException e) {
                         Slog.w(TAG, "Error bringing down backup stack");
+                        result = BackupTransport.TRANSPORT_ERROR;
                     }
                 }
             } else {
                 Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
+                result = BackupTransport.AGENT_ERROR;
             }
             tearDown(pkg);
+            return result;
         }
 
         private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) {
@@ -3232,16 +2924,6 @@
             }
         }
 
-        private void finalizeBackup(OutputStream out) {
-            try {
-                // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
-                byte[] eof = new byte[512 * 2]; // newly allocated == zero filled
-                out.write(eof);
-            } catch (IOException e) {
-                Slog.w(TAG, "Error attempting to finalize backup stream");
-            }
-        }
-
         private void writeAppManifest(PackageInfo pkg, File manifestFile,
                 boolean withApk, boolean withWidgets) throws IOException {
             // Manifest format. All data are strings ending in LF:
@@ -3332,7 +3014,7 @@
                             if (MORE_DEBUG) Slog.d(TAG, "Backup complete, killing host process");
                             mActivityManager.killApplicationProcess(app.processName, app.uid);
                         } else {
-                            if (MORE_DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName);
+                            if (MORE_DEBUG) Slog.d(TAG, "Not killing after backup: " + app.processName);
                         }
                     } catch (RemoteException e) {
                         Slog.d(TAG, "Lost app trying to shut down");
@@ -3340,9 +3022,18 @@
                 }
             }
         }
+    }
+
+    // Generic driver skeleton for full backup operations
+    abstract class FullBackupTask implements Runnable {
+        IFullBackupRestoreObserver mObserver;
+
+        FullBackupTask(IFullBackupRestoreObserver observer) {
+            mObserver = observer;
+        }
 
         // wrappers for observer use
-        void sendStartBackup() {
+        final void sendStartBackup() {
             if (mObserver != null) {
                 try {
                     mObserver.onStartBackup();
@@ -3353,7 +3044,7 @@
             }
         }
 
-        void sendOnBackupPackage(String name) {
+        final void sendOnBackupPackage(String name) {
             if (mObserver != null) {
                 try {
                     // TODO: use a more user-friendly name string
@@ -3365,7 +3056,7 @@
             }
         }
 
-        void sendEndBackup() {
+        final void sendEndBackup() {
             if (mObserver != null) {
                 try {
                     mObserver.onEndBackup();
@@ -3377,6 +3068,556 @@
         }
     }
 
+    // Full backup task variant used for adb backup
+    class PerformAdbBackupTask extends FullBackupTask {
+        FullBackupEngine mBackupEngine;
+        final AtomicBoolean mLatch;
+
+        ParcelFileDescriptor mOutputFile;
+        DeflaterOutputStream mDeflater;
+        boolean mIncludeApks;
+        boolean mIncludeObbs;
+        boolean mIncludeShared;
+        boolean mDoWidgets;
+        boolean mAllApps;
+        boolean mIncludeSystem;
+        boolean mCompress;
+        ArrayList<String> mPackages;
+        String mCurrentPassword;
+        String mEncryptPassword;
+        
+        PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, 
+                boolean includeApks, boolean includeObbs, boolean includeShared,
+                boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
+                boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
+            super(observer);
+            mLatch = latch;
+
+            mOutputFile = fd;
+            mIncludeApks = includeApks;
+            mIncludeObbs = includeObbs;
+            mIncludeShared = includeShared;
+            mDoWidgets = doWidgets;
+            mAllApps = doAllApps;
+            mIncludeSystem = doSystem;
+            mPackages = (packages == null)
+                    ? new ArrayList<String>()
+                    : new ArrayList<String>(Arrays.asList(packages));
+            mCurrentPassword = curPassword;
+            // when backing up, if there is a current backup password, we require that
+            // the user use a nonempty encryption password as well.  if one is supplied
+            // in the UI we use that, but if the UI was left empty we fall back to the
+            // current backup password (which was supplied by the user as well).
+            if (encryptPassword == null || "".equals(encryptPassword)) {
+                mEncryptPassword = curPassword;
+            } else {
+                mEncryptPassword = encryptPassword;
+            }
+            mCompress = doCompress;
+        }
+
+        void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
+            for (String pkgName : pkgNames) {
+                if (!set.containsKey(pkgName)) {
+                    try {
+                        PackageInfo info = mPackageManager.getPackageInfo(pkgName,
+                                PackageManager.GET_SIGNATURES);
+                        set.put(pkgName, info);
+                    } catch (NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
+                    }
+                }
+            }
+        }
+
+        private OutputStream emitAesBackupHeader(StringBuilder headerbuf,
+                OutputStream ofstream) throws Exception {
+            // User key will be used to encrypt the master key.
+            byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE);
+            SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt,
+                    PBKDF2_HASH_ROUNDS);
+
+            // the master key is random for each backup
+            byte[] masterPw = new byte[256 / 8];
+            mRng.nextBytes(masterPw);
+            byte[] checksumSalt = randomBytes(PBKDF2_SALT_SIZE);
+
+            // primary encryption of the datastream with the random key
+            Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES");
+            c.init(Cipher.ENCRYPT_MODE, masterKeySpec);
+            OutputStream finalOutput = new CipherOutputStream(ofstream, c);
+
+            // line 4: name of encryption algorithm
+            headerbuf.append(ENCRYPTION_ALGORITHM_NAME);
+            headerbuf.append('\n');
+            // line 5: user password salt [hex]
+            headerbuf.append(byteArrayToHex(newUserSalt));
+            headerbuf.append('\n');
+            // line 6: master key checksum salt [hex]
+            headerbuf.append(byteArrayToHex(checksumSalt));
+            headerbuf.append('\n');
+            // line 7: number of PBKDF2 rounds used [decimal]
+            headerbuf.append(PBKDF2_HASH_ROUNDS);
+            headerbuf.append('\n');
+
+            // line 8: IV of the user key [hex]
+            Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            mkC.init(Cipher.ENCRYPT_MODE, userKey);
+
+            byte[] IV = mkC.getIV();
+            headerbuf.append(byteArrayToHex(IV));
+            headerbuf.append('\n');
+
+            // line 9: master IV + key blob, encrypted by the user key [hex].  Blob format:
+            //    [byte] IV length = Niv
+            //    [array of Niv bytes] IV itself
+            //    [byte] master key length = Nmk
+            //    [array of Nmk bytes] master key itself
+            //    [byte] MK checksum hash length = Nck
+            //    [array of Nck bytes] master key checksum hash
+            //
+            // The checksum is the (master key + checksum salt), run through the
+            // stated number of PBKDF2 rounds
+            IV = c.getIV();
+            byte[] mk = masterKeySpec.getEncoded();
+            byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
+                    checksumSalt, PBKDF2_HASH_ROUNDS);
+
+            ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
+                    + checksum.length + 3);
+            DataOutputStream mkOut = new DataOutputStream(blob);
+            mkOut.writeByte(IV.length);
+            mkOut.write(IV);
+            mkOut.writeByte(mk.length);
+            mkOut.write(mk);
+            mkOut.writeByte(checksum.length);
+            mkOut.write(checksum);
+            mkOut.flush();
+            byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
+            headerbuf.append(byteArrayToHex(encryptedMk));
+            headerbuf.append('\n');
+
+            return finalOutput;
+        }
+
+        private void finalizeBackup(OutputStream out) {
+            try {
+                // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
+                byte[] eof = new byte[512 * 2]; // newly allocated == zero filled
+                out.write(eof);
+            } catch (IOException e) {
+                Slog.w(TAG, "Error attempting to finalize backup stream");
+            }
+        }
+
+        @Override
+        public void run() {
+            Slog.i(TAG, "--- Performing full-dataset adb backup ---");
+
+            TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
+            FullBackupObbConnection obbConnection = new FullBackupObbConnection();
+            obbConnection.establish();  // we'll want this later
+
+            sendStartBackup();
+
+            // doAllApps supersedes the package set if any
+            if (mAllApps) {
+                List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
+                        PackageManager.GET_SIGNATURES);
+                for (int i = 0; i < allPackages.size(); i++) {
+                    PackageInfo pkg = allPackages.get(i);
+                    // Exclude system apps if we've been asked to do so
+                    if (mIncludeSystem == true
+                            || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
+                        packagesToBackup.put(pkg.packageName, pkg);
+                    }
+                }
+            }
+
+            // If we're doing widget state as well, ensure that we have all the involved
+            // host & provider packages in the set
+            if (mDoWidgets) {
+                List<String> pkgs =
+                        AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_OWNER);
+                if (pkgs != null) {
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "Adding widget participants to backup set:");
+                        StringBuilder sb = new StringBuilder(128);
+                        sb.append("   ");
+                        for (String s : pkgs) {
+                            sb.append(' ');
+                            sb.append(s);
+                        }
+                        Slog.i(TAG, sb.toString());
+                    }
+                    addPackagesToSet(packagesToBackup, pkgs);
+                }
+            }
+
+            // Now process the command line argument packages, if any. Note that explicitly-
+            // named system-partition packages will be included even if includeSystem was
+            // set to false.
+            if (mPackages != null) {
+                addPackagesToSet(packagesToBackup, mPackages);
+            }
+
+            // Now we cull any inapplicable / inappropriate packages from the set
+            Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
+            while (iter.hasNext()) {
+                PackageInfo pkg = iter.next().getValue();
+                if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
+                        || pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
+                    // Cull any packages that have indicated that backups are not permitted, as well
+                    // as any explicit mention of the 'special' shared-storage agent package (we
+                    // handle that one at the end).
+                    iter.remove();
+                } else if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
+                        && (pkg.applicationInfo.backupAgentName == null)) {
+                    // Cull any packages that run as system-domain uids but do not define their
+                    // own backup agents
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "... ignoring non-agent system package " + pkg.packageName);
+                    }
+                    iter.remove();
+                }
+            }
+
+            // flatten the set of packages now so we can explicitly control the ordering
+            ArrayList<PackageInfo> backupQueue =
+                    new ArrayList<PackageInfo>(packagesToBackup.values());
+            FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
+            OutputStream out = null;
+
+            PackageInfo pkg = null;
+            try {
+                boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0);
+                OutputStream finalOutput = ofstream;
+
+                // Verify that the given password matches the currently-active
+                // backup password, if any
+                if (!backupPasswordMatches(mCurrentPassword)) {
+                    if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+                    return;
+                }
+
+                // Write the global file header.  All strings are UTF-8 encoded; lines end
+                // with a '\n' byte.  Actual backup data begins immediately following the
+                // final '\n'.
+                //
+                // line 1: "ANDROID BACKUP"
+                // line 2: backup file format version, currently "2"
+                // line 3: compressed?  "0" if not compressed, "1" if compressed.
+                // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
+                //
+                // When line 4 is not "none", then additional header data follows:
+                //
+                // line 5: user password salt [hex]
+                // line 6: master key checksum salt [hex]
+                // line 7: number of PBKDF2 rounds to use (same for user & master) [decimal]
+                // line 8: IV of the user key [hex]
+                // line 9: master key blob [hex]
+                //     IV of the master key, master key itself, master key checksum hash
+                //
+                // The master key checksum is the master key plus its checksum salt, run through
+                // 10k rounds of PBKDF2.  This is used to verify that the user has supplied the
+                // correct password for decrypting the archive:  the master key decrypted from
+                // the archive using the user-supplied password is also run through PBKDF2 in
+                // this way, and if the result does not match the checksum as stored in the
+                // archive, then we know that the user-supplied password does not match the
+                // archive's.
+                StringBuilder headerbuf = new StringBuilder(1024);
+
+                headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
+                headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n
+                headerbuf.append(mCompress ? "\n1\n" : "\n0\n");
+
+                try {
+                    // Set up the encryption stage if appropriate, and emit the correct header
+                    if (encrypting) {
+                        finalOutput = emitAesBackupHeader(headerbuf, finalOutput);
+                    } else {
+                        headerbuf.append("none\n");
+                    }
+
+                    byte[] header = headerbuf.toString().getBytes("UTF-8");
+                    ofstream.write(header);
+
+                    // Set up the compression stage feeding into the encryption stage (if any)
+                    if (mCompress) {
+                        Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
+                        finalOutput = new DeflaterOutputStream(finalOutput, deflater, true);
+                    }
+
+                    out = finalOutput;
+                } catch (Exception e) {
+                    // Should never happen!
+                    Slog.e(TAG, "Unable to emit archive header", e);
+                    return;
+                }
+
+                // Shared storage if requested
+                if (mIncludeShared) {
+                    try {
+                        pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
+                        backupQueue.add(pkg);
+                    } catch (NameNotFoundException e) {
+                        Slog.e(TAG, "Unable to find shared-storage backup handler");
+                    }
+                }
+
+                // Now actually run the constructed backup sequence
+                int N = backupQueue.size();
+                for (int i = 0; i < N; i++) {
+                    pkg = backupQueue.get(i);
+                    final boolean isSharedStorage =
+                            pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
+
+                    mBackupEngine = new FullBackupEngine(out, pkg.packageName, mIncludeApks);
+                    sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
+                    mBackupEngine.backupOnePackage(pkg);
+
+                    // after the app's agent runs to handle its private filesystem
+                    // contents, back up any OBB content it has on its behalf.
+                    if (mIncludeObbs) {
+                        boolean obbOkay = obbConnection.backupObbs(pkg, out);
+                        if (!obbOkay) {
+                            throw new RuntimeException("Failure writing OBB stack for " + pkg);
+                        }
+                    }
+                }
+
+                // Done!
+                finalizeBackup(out);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "App died during full backup");
+            } catch (Exception e) {
+                Slog.e(TAG, "Internal exception during full backup", e);
+            } finally {
+                try {
+                    if (out != null) out.close();
+                    mOutputFile.close();
+                } catch (IOException e) {
+                    /* nothing we can do about this */
+                }
+                synchronized (mCurrentOpLock) {
+                    mCurrentOperations.clear();
+                }
+                synchronized (mLatch) {
+                    mLatch.set(true);
+                    mLatch.notifyAll();
+                }
+                sendEndBackup();
+                obbConnection.tearDown();
+                if (DEBUG) Slog.d(TAG, "Full backup pass complete.");
+                mWakelock.release();
+            }
+        }
+    }
+
+    // Full backup task extension used for transport-oriented operation
+    class PerformFullTransportBackupTask extends FullBackupTask {
+        static final String TAG = "PFTBT";
+        ArrayList<PackageInfo> mPackages;
+        AtomicBoolean mLatch;
+
+        PerformFullTransportBackupTask(IFullBackupRestoreObserver observer, 
+                String[] whichPackages, AtomicBoolean latch) {
+            super(observer);
+            mLatch = latch;
+            mPackages = new ArrayList<PackageInfo>(whichPackages.length);
+
+            for (String pkg : whichPackages) {
+                try {
+                    PackageInfo info = mPackageManager.getPackageInfo(pkg,
+                            PackageManager.GET_SIGNATURES);
+                    if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
+                            || pkg.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
+                        // Cull any packages that have indicated that backups are not permitted,
+                        // as well as any explicit mention of the 'special' shared-storage agent
+                        // package (we handle that one at the end).
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "Ignoring opted-out package " + pkg);
+                        }
+                        continue;
+                    } else if ((info.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
+                            && (info.applicationInfo.backupAgentName == null)) {
+                        // Cull any packages that run as system-domain uids but do not define their
+                        // own backup agents
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "Ignoring non-agent system package " + pkg);
+                        }
+                        continue;
+                    }
+                    mPackages.add(info);
+                } catch (NameNotFoundException e) {
+                    Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            IBackupTransport transport = getTransport(mCurrentTransport);
+            if (transport == null) {
+                Slog.w(TAG, "Transport not present; full data backup not performed");
+                return;
+            }
+
+            // data from the app, passed to us for bridging to the transport
+            ParcelFileDescriptor[] enginePipes = null;
+
+            // Pipe through which we write data to the transport
+            ParcelFileDescriptor[] transportPipes = null;
+
+            try {
+                // Set up to send data to the transport
+                if (transport != null) {
+                    for (PackageInfo target : mPackages) {
+                        if (DEBUG) {
+                            Slog.i(TAG, "Initiating full-data transport backup of "
+                                    + target.packageName);
+                        }
+                        transportPipes = ParcelFileDescriptor.createPipe();
+
+                        // Tell the transport the data's coming
+                        int result = transport.performFullBackup(target, transportPipes[0]);
+                        if (result == BackupTransport.TRANSPORT_OK) {
+                            // The transport has its own copy of the read end of the pipe,
+                            // so close ours now
+                            transportPipes[0].close();
+                            transportPipes[0] = null;
+
+                            // Now set up the backup engine / data source end of things
+                            enginePipes = ParcelFileDescriptor.createPipe();
+                            AtomicBoolean runnerLatch = new AtomicBoolean(false);
+                            SinglePackageBackupRunner backupRunner =
+                                    new SinglePackageBackupRunner(enginePipes[1], target,
+                                            runnerLatch);
+                            // The runner dup'd the pipe half, so we close it here
+                            enginePipes[1].close();
+                            enginePipes[1] = null;
+
+                            // Spin off the runner to fetch the app's data and pipe it
+                            // into the engine pipes
+                            (new Thread(backupRunner, "package-backup-bridge")).start();
+
+                            // Read data off the engine pipe and pass it to the transport
+                            // pipe until we hit EOD on the input stream.
+                            FileInputStream in = new FileInputStream(
+                                    enginePipes[0].getFileDescriptor());
+                            FileOutputStream out = new FileOutputStream(
+                                    transportPipes[1].getFileDescriptor());
+                            byte[] buffer = new byte[8192];
+                            int nRead = 0;
+                            do {
+                                nRead = in.read(buffer);
+                                if (nRead > 0) {
+                                    out.write(buffer, 0, nRead);
+                                    result = transport.sendBackupData(nRead);
+                                }
+                            } while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
+
+                            // Done -- how did it turn out?
+                            if (result == BackupTransport.TRANSPORT_OK){
+                                result = transport.finishBackup();
+                            } else {
+                                Slog.w(TAG, "Error backing up " + target.packageName);
+                            }
+                        } else if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+                            if (DEBUG) {
+                                Slog.i(TAG, "Transport rejected backup of " + target.packageName
+                                        + ", skipping");
+                            }
+                            // do nothing, clean up, and continue looping
+                        } else {
+                            if (DEBUG) {
+                                Slog.i(TAG, "Transport failed; aborting backup");
+                                return;
+                            }
+                        }
+                        cleanUpPipes(transportPipes);
+                        cleanUpPipes(enginePipes);
+                    }
+
+                    if (DEBUG) {
+                        Slog.i(TAG, "Full backup completed.");
+                    }
+                }
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception trying full transport backup", e);
+            } finally {
+                cleanUpPipes(transportPipes);
+                cleanUpPipes(enginePipes);
+                synchronized (mLatch) {
+                    mLatch.set(true);
+                    mLatch.notifyAll();
+                }
+            }
+        }
+
+        void cleanUpPipes(ParcelFileDescriptor[] pipes) {
+            if (pipes != null) {
+                if (pipes[0] != null) {
+                    ParcelFileDescriptor fd = pipes[0];
+                    pipes[0] = null;
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Unable to close pipe!");
+                    }
+                }
+                if (pipes[1] != null) {
+                    ParcelFileDescriptor fd = pipes[1];
+                    pipes[1] = null;
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Unable to close pipe!");
+                    }
+                }
+            }
+        }
+
+        // Run the backup and pipe it back to the given socket -- expects to run on
+        // a standalone thread.  The  runner owns this half of the pipe, and closes
+        // it to indicate EOD to the other end.
+        class SinglePackageBackupRunner implements Runnable {
+            final ParcelFileDescriptor mOutput;
+            final PackageInfo mTarget;
+            final AtomicBoolean mLatch;
+
+            SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
+                    AtomicBoolean latch) throws IOException {
+                int oldfd = output.getFd();
+                mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
+                mTarget = target;
+                mLatch = latch;
+            }
+
+            @Override
+            public void run() {
+                try {
+                    FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
+                    FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, false);
+                    engine.backupOnePackage(mTarget);
+                } catch (Exception e) {
+                    Slog.e(TAG, "Exception during full package backup of " + mTarget);
+                } finally {
+                    synchronized (mLatch) {
+                        mLatch.set(true);
+                        mLatch.notifyAll();
+                    }
+                    try {
+                        mOutput.close();
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Error closing transport pipe in runner");
+                    }
+                }
+            }
+            
+        }
+    }
 
     // ----- Full restore from a file/socket -----
 
@@ -3410,7 +3651,7 @@
         ACCEPT_IF_APK
     }
 
-    class PerformFullRestoreTask extends ObbServiceClient implements Runnable {
+    class PerformFullRestoreTask implements Runnable {
         ParcelFileDescriptor mInputFile;
         String mCurrentPassword;
         String mDecryptPassword;
@@ -3877,7 +4118,7 @@
                                         Slog.d(TAG, "system process agent - spinning a thread");
                                         RestoreFileRunnable runner = new RestoreFileRunnable(
                                                 mAgent, info, mPipes[0], token);
-                                        new Thread(runner).start();
+                                        new Thread(runner, "restore-sys-runner").start();
                                     } else {
                                         mAgent.doRestoreFile(mPipes[0], info.size, info.type,
                                                 info.domain, info.path, info.mode, info.mtime,
@@ -4847,7 +5088,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 +5143,7 @@
                 return;
             }
 
-            mStatus = BackupConstants.TRANSPORT_OK;
+            mStatus = BackupTransport.TRANSPORT_OK;
             executeNextState(RestoreState.DOWNLOAD_DATA);
         }
 
@@ -4917,7 +5158,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 +5167,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;
             }
@@ -4937,18 +5178,20 @@
 
         void restorePmMetadata() {
             try {
-                String packageName = mTransport.nextRestorePackage();
+                RestoreDescription desc = mTransport.nextRestorePackage();
+                // TODO: handle full-data stream restore payloads
+                String packageName = desc.getPackageName();
                 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 +5222,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 +5230,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;
@@ -5005,7 +5248,9 @@
 
         void restoreNextAgent() {
             try {
-                String packageName = mTransport.nextRestorePackage();
+                final RestoreDescription desc = mTransport.nextRestorePackage();
+                // TODO: handle full-data stream restore payloads
+                String packageName = desc.getPackageName();
 
                 if (packageName == null) {
                     Slog.e(TAG, "Error getting next restore package");
@@ -5118,7 +5363,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 +5451,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 +5695,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);
@@ -5684,13 +5929,16 @@
         return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
     }
 
-    // Run a *full* backup pass for the given package, writing the resulting data stream
+    // Run a *full* backup pass for the given packages, writing the resulting data stream
     // to the supplied file descriptor.  This method is synchronous and does not return
     // to the caller until the backup has been completed.
+    //
+    // This is the variant used by 'adb backup'; it requires on-screen confirmation
+    // by the user because it can be used to offload data over untrusted USB.
     @Override
     public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
             boolean includeObbs, boolean includeShared, boolean doWidgets,
-            boolean doAllApps, boolean includeSystem, String[] pkgList) {
+            boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
@@ -5725,7 +5973,7 @@
             Slog.i(TAG, "Beginning full backup...");
 
             FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
-                    includeShared, doWidgets, doAllApps, includeSystem, pkgList);
+                    includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
             final int token = generateToken();
             synchronized (mFullConfirmations) {
                 mFullConfirmations.put(token, params);
@@ -5759,6 +6007,36 @@
         }
     }
 
+    @Override
+    public void fullTransportBackup(String[] pkgNames) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
+                "fullTransportBackup");
+
+        final int callingUserHandle = UserHandle.getCallingUserId();
+        if (callingUserHandle != UserHandle.USER_OWNER) {
+            throw new IllegalStateException("Restore supported only for the device owner");
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "fullTransportBackup()");
+        }
+
+        AtomicBoolean latch = new AtomicBoolean(false);
+        PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames, latch);
+        (new Thread(task, "full-transport-master")).start();
+        synchronized (latch) {
+            try {
+                while (latch.get() == false) {
+                    latch.wait();
+                }
+            } catch (InterruptedException e) {}
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Done with full transport backup.");
+        }
+    }
+
+    @Override
     public void fullRestore(ParcelFileDescriptor fd) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");
 
@@ -5876,8 +6154,8 @@
 
                     if (allow) {
                         final int verb = params instanceof FullBackupParams
-                                ? MSG_RUN_FULL_BACKUP
-                                : MSG_RUN_FULL_RESTORE;
+                                ? MSG_RUN_ADB_BACKUP
+                                : MSG_RUN_ADB_RESTORE;
 
                         params.observer = observer;
                         params.curPassword = curPassword;
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index aeb195f..912a181 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -18,6 +18,7 @@
 
 import android.database.ContentObserver;
 import android.os.BatteryStats;
+
 import com.android.internal.app.IBatteryStats;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.Light;
@@ -29,6 +30,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.BatteryProperties;
 import android.os.Binder;
 import android.os.FileUtils;
@@ -83,7 +85,7 @@
  * service asynchronously itself.
  * </p>
  */
-public final class BatteryService extends Binder {
+public final class BatteryService extends SystemService {
     private static final String TAG = BatteryService.class.getSimpleName();
 
     private static final boolean DEBUG = false;
@@ -140,10 +142,12 @@
 
     private boolean mSentLowBatteryBroadcast = false;
 
-    public BatteryService(Context context, LightsManager lightsManager) {
+    public BatteryService(Context context) {
+        super(context);
+
         mContext = context;
         mHandler = new Handler(true /*async*/);
-        mLed = new Led(context, lightsManager);
+        mLed = new Led(context, getLocalService(LightsManager.class));
         mBatteryStats = BatteryStatsService.getService();
 
         mCriticalBatteryLevel = mContext.getResources().getInteger(
@@ -160,7 +164,10 @@
             mInvalidChargerObserver.startObserving(
                     "DEVPATH=/devices/virtual/switch/invalid_charger");
         }
+    }
 
+    @Override
+    public void onStart() {
         IBinder b = ServiceManager.getService("batteryproperties");
         final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
                 IBatteryPropertiesRegistrar.Stub.asInterface(b);
@@ -169,28 +176,34 @@
         } catch (RemoteException e) {
             // Should never happen.
         }
+
+        publishBinderService("battery", new BinderService());
+        publishLocalService(BatteryManagerInternal.class, new LocalService());
     }
 
-    void systemReady() {
-        // check our power situation now that it is safe to display the shutdown dialog.
-        synchronized (mLock) {
-            ContentObserver obs = new ContentObserver(mHandler) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    synchronized (mLock) {
-                        updateBatteryWarningLevelLocked();
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+            // check our power situation now that it is safe to display the shutdown dialog.
+            synchronized (mLock) {
+                ContentObserver obs = new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            updateBatteryWarningLevelLocked();
+                        }
                     }
-                }
-            };
-            final ContentResolver resolver = mContext.getContentResolver();
-            resolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
-                    false, obs, UserHandle.USER_ALL);
-            updateBatteryWarningLevelLocked();
+                };
+                final ContentResolver resolver = mContext.getContentResolver();
+                resolver.registerContentObserver(Settings.Global.getUriFor(
+                        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+                        false, obs, UserHandle.USER_ALL);
+                updateBatteryWarningLevelLocked();
+            }
         }
     }
 
-    void updateBatteryWarningLevelLocked() {
+    private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
         int defWarnLevel = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
@@ -207,15 +220,6 @@
         processValuesLocked(true);
     }
 
-    /**
-     * Returns true if the device is plugged into any of the specified plug types.
-     */
-    public boolean isPowered(int plugTypeSet) {
-        synchronized (mLock) {
-            return isPoweredLocked(plugTypeSet);
-        }
-    }
-
     private boolean isPoweredLocked(int plugTypeSet) {
         // assume we are powered if battery state is unknown so
         // the "stay on while plugged in" option will work.
@@ -234,34 +238,7 @@
         return false;
     }
 
-    /**
-     * Returns the current plug type.
-     */
-    public int getPlugType() {
-        synchronized (mLock) {
-            return mPlugType;
-        }
-    }
-
-    /**
-     * Returns battery level as a percentage.
-     */
-    public int getBatteryLevel() {
-        synchronized (mLock) {
-            return mBatteryProps.batteryLevel;
-        }
-    }
-
-    /**
-     * Returns whether we currently consider the battery level to be low.
-     */
-    public boolean getBatteryLevelLow() {
-        synchronized (mLock) {
-            return mBatteryLevelLow;
-        }
-    }
-
-    public boolean shouldSendBatteryLowLocked() {
+    private boolean shouldSendBatteryLowLocked() {
         final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
         final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
 
@@ -277,15 +254,6 @@
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
-    /**
-     * Returns a non-zero value if an  unsupported charger is attached.
-     */
-    public int getInvalidCharger() {
-        synchronized (mLock) {
-            return mInvalidCharger;
-        }
-    }
-
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
@@ -640,17 +608,7 @@
         }
     }
 
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                != PackageManager.PERMISSION_GRANTED) {
-
-            pw.println("Permission Denial: can't dump Battery service from from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return;
-        }
-
+    private void dumpInternal(PrintWriter pw, String[] args) {
         synchronized (mLock) {
             if (args == null || args.length == 0 || "-a".equals(args[0])) {
                 pw.println("Current Battery Service state:");
@@ -801,4 +759,57 @@
             }
        }
     }
+
+    private final class BinderService extends Binder {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+
+                pw.println("Permission Denial: can't dump Battery service from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+
+            dumpInternal(pw, args);
+        }
+    }
+
+    private final class LocalService extends BatteryManagerInternal {
+        @Override
+        public boolean isPowered(int plugTypeSet) {
+            synchronized (mLock) {
+                return isPoweredLocked(plugTypeSet);
+            }
+        }
+
+        @Override
+        public int getPlugType() {
+            synchronized (mLock) {
+                return mPlugType;
+            }
+        }
+
+        @Override
+        public int getBatteryLevel() {
+            synchronized (mLock) {
+                return mBatteryProps.batteryLevel;
+            }
+        }
+
+        @Override
+        public boolean getBatteryLevelLow() {
+            synchronized (mLock) {
+                return mBatteryLevelLow;
+            }
+        }
+
+        @Override
+        public int getInvalidCharger() {
+            synchronized (mLock) {
+                return mInvalidCharger;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e2a8ca2..07c9048 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -308,6 +308,10 @@
     }
 
     public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
+        if (callback == null) {
+            Log.w(TAG, "Callback is null in registerAdapter");
+            return null;
+        }
         Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
         msg.obj = callback;
         mHandler.sendMessage(msg);
@@ -317,6 +321,10 @@
     }
 
     public void unregisterAdapter(IBluetoothManagerCallback callback) {
+        if (callback == null) {
+            Log.w(TAG, "Callback is null in unregisterAdapter");
+            return;
+        }
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
                                                 "Need BLUETOOTH permission");
         Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e39e077..19fff55 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -45,7 +45,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -410,7 +409,8 @@
 
     /**
      * used to remove a network request, either a listener or a real request
-     * includes a NetworkRequest
+     * arg1 = UID of caller
+     * obj  = NetworkRequest
      */
     private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
 
@@ -911,8 +911,6 @@
             switch (config.radio) {
                 case TYPE_DUMMY:
                     return new DummyDataStateTracker(targetNetworkType, config.name);
-                case TYPE_BLUETOOTH:
-                    return BluetoothTetheringDataTracker.getInstance();
                 case TYPE_WIMAX:
                     return makeWimaxStateTracker(mContext, mTrackerHandler);
                 case TYPE_PROXY:
@@ -3336,10 +3334,15 @@
         }
     }
 
-    private void handleReleaseNetworkRequest(NetworkRequest request) {
-        if (DBG) log("releasing NetworkRequest " + request);
-        NetworkRequestInfo nri = mNetworkRequests.remove(request);
+    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
+        NetworkRequestInfo nri = mNetworkRequests.get(request);
         if (nri != null) {
+            if (nri.mUid != callingUid) {
+                if (DBG) log("Attempt to release unowned NetworkRequest " + request);
+                return;
+            }
+            if (DBG) log("releasing NetworkRequest " + request);
+            mNetworkRequests.remove(request);
             // tell the network currently servicing this that it's no longer interested
             NetworkAgentInfo affectedNetwork = mNetworkForRequestId.get(nri.request.requestId);
             if (affectedNetwork != null) {
@@ -3485,7 +3488,7 @@
                     break;
                 }
                 case EVENT_RELEASE_NETWORK_REQUEST: {
-                    handleReleaseNetworkRequest((NetworkRequest) msg.obj);
+                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                     break;
                 }
             }
@@ -4437,7 +4440,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");
                             }
@@ -4711,7 +4716,7 @@
                         LinkProperties lp = mCs.getLinkPropertiesForTypeInternal(
                                 ConnectivityManager.TYPE_MOBILE_HIPRI);
                         boolean linkHasIpv4 = lp.hasIPv4Address();
-                        boolean linkHasIpv6 = lp.hasIPv6Address();
+                        boolean linkHasIpv6 = lp.hasGlobalIPv6Address();
                         log("isMobileOk: linkHasIpv4=" + linkHasIpv4
                                 + " linkHasIpv6=" + linkHasIpv6);
 
@@ -4949,17 +4954,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
@@ -5454,8 +5466,8 @@
 
     @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST,
-                networkRequest));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
+                0, networkRequest));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/DisplayThread.java b/services/core/java/com/android/server/DisplayThread.java
index 528ba0a..aa0a805 100644
--- a/services/core/java/com/android/server/DisplayThread.java
+++ b/services/core/java/com/android/server/DisplayThread.java
@@ -41,14 +41,14 @@
     }
 
     public static DisplayThread get() {
-        synchronized (UiThread.class) {
+        synchronized (DisplayThread.class) {
             ensureThreadLocked();
             return sInstance;
         }
     }
 
     public static Handler getHandler() {
-        synchronized (UiThread.class) {
+        synchronized (DisplayThread.class) {
             ensureThreadLocked();
             return sHandler;
         }
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/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java
deleted file mode 100644
index acc6abe..0000000
--- a/services/core/java/com/android/server/IdleMaintenanceService.java
+++ /dev/null
@@ -1,818 +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.server;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.maintenance.IIdleCallback;
-import android.app.maintenance.IIdleService;
-import android.app.maintenance.IdleService;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * This service observes the device state and when applicable sends
- * broadcasts at the beginning and at the end of a period during which
- * observers can perform idle maintenance tasks. Typical use of the
- * idle maintenance is to perform somehow expensive tasks that can be
- * postponed to a moment when they will not degrade user experience.
- *
- * The current implementation is very simple. The start of a maintenance
- * window is announced if: the screen is off or showing a dream AND the
- * battery level is more than twenty percent AND at least one hour passed
- * activity).
- *
- * The end of a maintenance window is announced only if: a start was
- * announced AND the screen turned on or a dream was stopped.
- *
- * Method naming note:
- * Methods whose name ends with "Tm" must only be called from the main thread.
- */
-public class IdleMaintenanceService extends BroadcastReceiver {
-
-    private static final boolean DEBUG = false;
-
-    private static final String TAG = IdleMaintenanceService.class.getSimpleName();
-
-    private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
-
-    private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day
-
-    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent
-
-    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent
-
-    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
-
-    private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
-
-    private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
-
-    private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
-        "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
-
-    private static final String ACTION_FORCE_IDLE_MAINTENANCE =
-        "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
-
-    static final int MSG_OP_COMPLETE = 1;
-    static final int MSG_IDLE_FINISHED = 2;
-    static final int MSG_TIMEOUT = 3;
-
-    // when a timeout happened, what were we expecting?
-    static final int VERB_BINDING = 1;
-    static final int VERB_IDLING = 2;
-    static final int VERB_ENDING = 3;
-
-    // What are our relevant timeouts / allocated slices?
-    static final long OP_TIMEOUT = 8 * 1000;  // 8 seconds to bind or ack the start
-    static final long IDLE_TIMESLICE = 10 * 60 * 1000;  // ten minutes for each idler
-
-    private final AlarmManager mAlarmService;
-    private final BatteryService mBatteryService;
-    private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
-    private final Context mContext;
-    private final WakeLock mWakeLock;
-    private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid());
-
-    private long mLastIdleMaintenanceStartTimeMillis;
-    private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
-    private boolean mIdleMaintenanceStarted;
-
-    final IdleCallback mCallback;
-    final Handler mHandler;
-
-    final Random mTokenGenerator = new Random();
-
-    int makeToken() {
-        int token;
-        do  {
-            token = mTokenGenerator.nextInt(Integer.MAX_VALUE);
-        } while (token == 0);
-        return token;
-    }
-
-    class ActiveTask {
-        public IdleServiceInfo who;
-        public int verb;
-        public int token;
-
-        ActiveTask(IdleServiceInfo target, int action) {
-            who = target;
-            verb = action;
-            token = makeToken();
-        }
-
-        @Override
-        public String toString() {
-            return "ActiveTask{" + Integer.toHexString(this.hashCode())
-                    + " : verb=" + verb
-                    + " : token=" + token
-                    + " : "+ who + "}";
-        }
-    }
-
-    // What operations are in flight?
-    final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>();
-
-    // Idle service queue management
-    class IdleServiceInfo {
-        public final ComponentName componentName;
-        public final int uid;
-        public IIdleService service;
-
-        IdleServiceInfo(ResolveInfo info, ComponentName cname) {
-            componentName = cname;  // derived from 'info' but this avoids an extra object
-            uid = info.serviceInfo.applicationInfo.uid;
-            service = null;
-        }
-
-        @Override
-        public int hashCode() {
-            return componentName.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return "IdleServiceInfo{" + componentName
-                    + " / " + (service == null ? "null" : service.asBinder()) + "}";
-        }
-    }
-
-    final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices =
-            new ArrayMap<ComponentName, IdleServiceInfo>();
-    final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>();
-    IdleServiceInfo mCurrentIdler;  // set when we've committed to launching an idler
-    IdleServiceInfo mLastIdler;     // end of queue when idling begins
-
-    void reportNoTimeout(int token, boolean result) {
-        final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token);
-        mHandler.sendMessage(msg);
-    }
-
-    // Binder acknowledgment trampoline
-    class IdleCallback extends IIdleCallback.Stub {
-        @Override
-        public void acknowledgeStart(int token, boolean result) throws RemoteException {
-            reportNoTimeout(token, result);
-        }
-
-        @Override
-        public void acknowledgeStop(int token) throws RemoteException {
-            reportNoTimeout(token, false);
-        }
-
-        @Override
-        public void idleFinished(int token) throws RemoteException {
-            if (DEBUG) {
-                Slog.v(TAG, "idleFinished: " + token);
-            }
-            final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token);
-            mHandler.sendMessage(msg);
-        }
-    }
-
-    // Stuff that we run on a Handler
-    class IdleHandler extends Handler {
-        public IdleHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            final int token = msg.arg2;
-
-            switch (msg.what) {
-                case MSG_OP_COMPLETE: {
-                    if (DEBUG) {
-                        Slog.i(TAG, "MSG_OP_COMPLETE of " + token);
-                    }
-                    ActiveTask task = mPendingOperations.get(token);
-                    if (task != null) {
-                        mPendingOperations.remove(token);
-                        removeMessages(MSG_TIMEOUT);
-
-                        handleOpCompleteTm(task, msg.arg1);
-                    } else {
-                        // Can happen in a race between timeout and actual
-                        // (belated) completion of a "begin idling" or similar
-                        // operation.  In that state we've already processed the
-                        // timeout, so we intentionally no-op here.
-                        if (DEBUG) {
-                            Slog.w(TAG, "Belated op-complete of " + token);
-                        }
-                    }
-                    break;
-                }
-
-                case MSG_IDLE_FINISHED: {
-                    if (DEBUG) {
-                        Slog.i(TAG, "MSG_IDLE_FINISHED of " + token);
-                    }
-                    ActiveTask task = mPendingOperations.get(token);
-                    if (task != null) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "... removing task " + token);
-                        }
-                        mPendingOperations.remove(token);
-                        removeMessages(MSG_TIMEOUT);
-
-                        handleIdleFinishedTm(task);
-                    } else {
-                        // Can happen "legitimately" from an app explicitly calling
-                        // idleFinished() after already having been told that its slice
-                        // has ended.
-                        if (DEBUG) {
-                            Slog.w(TAG, "Belated idle-finished of " + token);
-                        }
-                    }
-                    break;
-                }
-
-                case MSG_TIMEOUT: {
-                    if (DEBUG) {
-                        Slog.i(TAG, "MSG_TIMEOUT of " + token);
-                    }
-                    ActiveTask task = mPendingOperations.get(token);
-                    if (task != null) {
-                        mPendingOperations.remove(token);
-                        removeMessages(MSG_OP_COMPLETE);
-
-                        handleTimeoutTm(task);
-                    } else {
-                        // This one should not happen; we flushed timeout messages
-                        // whenever we entered a state after which we have established
-                        // that they are not appropriate.
-                        Slog.w(TAG, "Unexpected timeout of " + token);
-                    }
-                    break;
-                }
-
-                default:
-                    Slog.w(TAG, "Unknown message: " + msg.what);
-            }
-        }
-    }
-
-    void handleTimeoutTm(ActiveTask task) {
-        switch (task.verb) {
-        case VERB_BINDING: {
-            // We were trying to bind to this service, but it wedged or otherwise
-            // failed to respond in time.  Let it stay in the queue for the next
-            // time around, but just give up on it for now and go on to the next.
-            startNextIdleServiceTm();
-            break;
-        }
-        case VERB_IDLING: {
-            // The service has reached the end of its designated idle timeslice.
-            // This is not considered an error.
-            if (DEBUG) {
-                Slog.i(TAG, "Idler reached end of timeslice: " + task.who);
-            }
-            sendEndIdleTm(task.who);
-            break;
-        }
-        case VERB_ENDING: {
-            if (mCurrentIdler == task.who) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Task timed out when ending; unbind needed");
-                }
-                handleIdleFinishedTm(task);
-            } else {
-                if (DEBUG) {
-                    Slog.w(TAG, "Ending timeout for non-current idle service!");
-                }
-            }
-            break;
-        }
-        default: {
-            Slog.w(TAG, "Unknown timeout state " + task.verb);
-            break;
-        }
-        }
-    }
-
-    void handleOpCompleteTm(ActiveTask task, int result) {
-        if (DEBUG) {
-            Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result);
-        }
-        if (task.verb == VERB_IDLING) {
-            // If the service was told to begin idling and responded positively, then
-            // it has begun idling and will eventually either explicitly finish, or
-            // reach the end of its allotted timeslice.  It's running free now, so we
-            // just schedule the idle-expiration timeout under the token it's already been
-            // given and let it keep going.
-            if (result != 0) {
-                scheduleOpTimeoutTm(task);
-            } else {
-                // The idle service has indicated that it does not, in fact,
-                // need to run at present, so we immediately indicate that it's
-                // to finish idling, and go on to the next idler.
-                if (DEBUG) {
-                    Slog.i(TAG, "Idler declined idling; moving along");
-                }
-                sendEndIdleTm(task.who);
-            }
-        } else {
-            // In the idling case, the task will be cleared either as the result of a timeout
-            // or of an explicit idleFinished().  For all other operations (binding, ending) we
-            // are done with the task as such, so we remove it from our bookkeeping.
-            if (DEBUG) {
-                Slog.i(TAG, "Clearing task " + task);
-            }
-            mPendingOperations.remove(task.token);
-            if (task.verb == VERB_ENDING) {
-                // The last bit of handshaking around idle cessation for this target
-                handleIdleFinishedTm(task);
-            }
-        }
-    }
-
-    void handleIdleFinishedTm(ActiveTask task) {
-        final IdleServiceInfo who = task.who;
-        if (who == mCurrentIdler) {
-            if (DEBUG) {
-                Slog.i(TAG, "Current idler has finished: " + who);
-                Slog.i(TAG, "Attributing wakelock to system work source");
-            }
-            mContext.unbindService(mConnection);
-            startNextIdleServiceTm();
-        } else {
-            Slog.w(TAG, "finish from non-current idle service? " + who);
-        }
-    }
-
-    void updateIdleServiceQueueTm() {
-        if (DEBUG) {
-            Slog.i(TAG, "Updating idle service queue");
-        }
-        PackageManager pm = mContext.getPackageManager();
-        Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
-        List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0);
-        for (ResolveInfo info : services) {
-            if (info.serviceInfo != null) {
-                if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) {
-                    final ComponentName componentName = new ComponentName(
-                            info.serviceInfo.packageName,
-                            info.serviceInfo.name);
-                    if (DEBUG) {
-                        Slog.i(TAG, "   - " + componentName);
-                    }
-                    if (!mIdleServices.containsKey(componentName)) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "      + not known; adding");
-                        }
-                        IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName);
-                        mIdleServices.put(componentName, serviceInfo);
-                        mIdleServiceQueue.add(serviceInfo);
-                    }
-                } else {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Idle service " + info.serviceInfo
-                                + " does not have required permission; ignoring");
-                    }
-                }
-            }
-        }
-    }
-
-    void startNextIdleServiceTm() {
-        mWakeLock.setWorkSource(mSystemWorkSource);
-
-        if (mLastIdler == null) {
-            // we've run the queue; nothing more to do until the next idle interval.
-            if (DEBUG) {
-                Slog.i(TAG, "Queue already drained; nothing more to do");
-            }
-            return;
-        }
-
-        if (DEBUG) {
-            Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler);
-            if (mIdleServiceQueue.size() > 0) {
-                int i = 0;
-                Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):");
-                for (IdleServiceInfo info : mIdleServiceQueue) {
-                    Slog.i(TAG, "   " + i + " : " + info);
-                    i++;
-                }
-            }
-        }
-        if (mCurrentIdler != mLastIdler) {
-            if (mIdleServiceQueue.size() > 0) {
-                IdleServiceInfo target = mIdleServiceQueue.pop();
-                if (DEBUG) {
-                    Slog.i(TAG, "starting next idle service " + target);
-                }
-                Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
-                idleIntent.setComponent(target.componentName);
-                mCurrentIdler = target;
-                ActiveTask task = new ActiveTask(target, VERB_BINDING);
-                scheduleOpTimeoutTm(task);
-                boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER);
-                if (!bindOk) {
-                    if (DEBUG) {
-                        Slog.w(TAG, "bindService() to " + target.componentName
-                                + " failed");
-                    }
-                } else {
-                    mIdleServiceQueue.add(target);  // at the end for next time
-                    if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); }
-                    mWakeLock.setWorkSource(new WorkSource(target.uid));
-                }
-            } else {
-                // Queue is empty but mLastIdler is non-null -- eeep.  Clear *everything*
-                // and wind up until the next time around.
-                Slog.e(TAG, "Queue unexpectedly empty; resetting.  last="
-                        + mLastIdler + " cur=" + mCurrentIdler);
-                mHandler.removeMessages(MSG_TIMEOUT);
-                mPendingOperations.clear();
-                stopIdleMaintenanceTm();
-            }
-        } else {
-            // we've reached the place we started, so mark the queue as drained
-            if (DEBUG) {
-                Slog.i(TAG, "Reached end of queue.");
-            }
-            stopIdleMaintenanceTm();
-        }
-    }
-
-    void sendStartIdleTm(IdleServiceInfo who) {
-        ActiveTask task = new ActiveTask(who, VERB_IDLING);
-        scheduleOpTimeoutTm(task);
-        try {
-            who.service.startIdleMaintenance(mCallback, task.token);
-        } catch (RemoteException e) {
-            // We bound to it, but now we can't reach it.  Bail and go on to the
-            // next service.
-            mContext.unbindService(mConnection);
-            if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
-            mHandler.removeMessages(MSG_TIMEOUT);
-            startNextIdleServiceTm();
-        }
-    }
-
-    void sendEndIdleTm(IdleServiceInfo who) {
-        ActiveTask task = new ActiveTask(who, VERB_ENDING);
-        scheduleOpTimeoutTm(task);
-        if (DEBUG) {
-            Slog.i(TAG, "Sending end-idle to " + who);
-        }
-        try {
-            who.service.stopIdleMaintenance(mCallback, task.token);
-        } catch (RemoteException e) {
-            // We bound to it, but now we can't reach it.  Bail and go on to the
-            // next service.
-            mContext.unbindService(mConnection);
-            if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
-            mHandler.removeMessages(MSG_TIMEOUT);
-            startNextIdleServiceTm();
-        }
-    }
-
-    ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) {
-                Slog.i(TAG, "onServiceConnected(" + name + ")");
-            }
-            IdleServiceInfo info = mIdleServices.get(name);
-            if (info != null) {
-                // Bound!  Cancel the bind timeout
-                mHandler.removeMessages(MSG_TIMEOUT);
-                // Now tell it to start its idle work
-                info.service = IIdleService.Stub.asInterface(service);
-                sendStartIdleTm(info);
-            } else {
-                // We bound to a service we don't know about.  That's ungood.
-                Slog.e(TAG, "Connected to unexpected component " + name);
-                mContext.unbindService(this);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) {
-                Slog.i(TAG, "onServiceDisconnected(" + name + ")");
-            }
-            IdleServiceInfo who = mIdleServices.get(name);
-            if (who == mCurrentIdler) {
-                // Hm, okay; they didn't tell us they were finished but they
-                // went away.  Crashed, probably.  Oh well.  They're gone, so
-                // we can't finish them cleanly; just force things along.
-                Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler);
-                mContext.unbindService(this);
-                mHandler.removeMessages(MSG_TIMEOUT);
-                startNextIdleServiceTm();
-            } else {
-                // Not the current idler, so we don't interrupt our process...
-                if (DEBUG) {
-                    Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name);
-                }
-            }
-        }
-    };
-
-    // Schedules a timeout / end-of-work based on the task verb
-    void scheduleOpTimeoutTm(ActiveTask task) {
-        final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT;
-        if (DEBUG) {
-            Slog.i(TAG, "Scheduling timeout (token " + task.token
-                    + " : verb " + task.verb + ") for " + task + " in " + timeoutMillis);
-        }
-        mPendingOperations.put(task.token, task);
-        mHandler.removeMessages(MSG_TIMEOUT);
-        final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token);
-        mHandler.sendMessageDelayed(msg, timeoutMillis);
-    }
-
-    // -------------------------------------------------------------------------------
-    public IdleMaintenanceService(Context context, BatteryService batteryService) {
-        mContext = context;
-        mBatteryService = batteryService;
-
-        mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
-        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
-        mHandler = new IdleHandler(mContext.getMainLooper());
-        mCallback = new IdleCallback();
-
-        Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
-        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0,
-                intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
-        register(mHandler);
-    }
-
-    public void register(Handler handler) {
-        IntentFilter intentFilter = new IntentFilter();
-
-        // Alarm actions.
-        intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
-
-        // Battery actions.
-        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
-
-        // Screen actions.
-        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
-        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-
-        // Dream actions.
-        intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
-        intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
-
-        mContext.registerReceiverAsUser(this, UserHandle.ALL,
-                intentFilter, null, mHandler);
-
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE);
-        mContext.registerReceiverAsUser(this, UserHandle.ALL,
-                intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler);
-    }
-
-    private void scheduleUpdateIdleMaintenanceState(long delayMillis) {
-        final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
-        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis,
-                mUpdateIdleMaintenanceStatePendingIntent);
-    }
-
-    private void unscheduleUpdateIdleMaintenanceState() {
-        mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
-    }
-
-    private void updateIdleMaintenanceStateTm(boolean noisy) {
-        if (mIdleMaintenanceStarted) {
-            // Idle maintenance can be interrupted by user activity, or duration
-            // time out, or low battery.
-            final boolean batteryOk
-                    = batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning();
-            if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) {
-                unscheduleUpdateIdleMaintenanceState();
-                mIdleMaintenanceStarted = false;
-                // We stopped since we don't have enough battery or timed out but the
-                // user is not using the device, so we should be able to run maintenance
-                // in the next maintenance window since the battery may be charged
-                // without interaction and the min interval between maintenances passed.
-                if (!batteryOk) {
-                    scheduleUpdateIdleMaintenanceState(
-                            getNextIdleMaintenanceIntervalStartFromNow());
-                }
-
-                EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
-                        mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
-                        isBatteryCharging() ? 1 : 0);
-                scheduleIdleFinishTm();
-            }
-        } else if (deviceStatePermitsIdleMaintenanceStart(noisy)
-                && lastUserActivityPermitsIdleMaintenanceStart(noisy)
-                && lastRunPermitsIdleMaintenanceStart(noisy)) {
-            // Now that we started idle maintenance, we should schedule another
-            // update for the moment when the idle maintenance times out.
-            scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
-            mIdleMaintenanceStarted = true;
-            EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
-                    mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
-                    isBatteryCharging() ? 1 : 0);
-            mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
-            startIdleMaintenanceTm();
-        } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
-             if (lastRunPermitsIdleMaintenanceStart(noisy)) {
-                // The user does not use the device and we did not run maintenance in more
-                // than the min interval between runs, so schedule an update - maybe the
-                // battery will be charged latter.
-                scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
-             } else {
-                 // The user does not use the device but we have run maintenance in the min
-                 // interval between runs, so schedule an update after the min interval ends.
-                 scheduleUpdateIdleMaintenanceState(
-                         getNextIdleMaintenanceIntervalStartFromNow());
-             }
-        }
-    }
-
-    void startIdleMaintenanceTm() {
-        if (DEBUG) {
-            Slog.i(TAG, "*** Starting idle maintenance ***");
-        }
-        if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
-        mWakeLock.setWorkSource(mSystemWorkSource);
-        mWakeLock.acquire();
-        updateIdleServiceQueueTm();
-        mCurrentIdler = null;
-        mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null;
-        startNextIdleServiceTm();
-    }
-
-    // Start a graceful wind-down of the idle maintenance state: end the current idler
-    // and pretend that we've finished running the queue.  If there's no current idler,
-    // this is a no-op.
-    void scheduleIdleFinishTm() {
-        if (mCurrentIdler != null) {
-            if (DEBUG) {
-                Slog.i(TAG, "*** Finishing idle maintenance ***");
-            }
-            mLastIdler = mCurrentIdler;
-            sendEndIdleTm(mCurrentIdler);
-        } else {
-            if (DEBUG) {
-                Slog.w(TAG, "Asked to finish idle maintenance but we're done already");
-            }
-        }
-    }
-
-    // Actual finalization of the idle maintenance sequence
-    void stopIdleMaintenanceTm() {
-        if (mLastIdler != null) {
-            if (DEBUG) {
-                Slog.i(TAG, "*** Idle maintenance shutdown ***");
-            }
-            mWakeLock.setWorkSource(mSystemWorkSource);
-            mLastIdler = mCurrentIdler = null;
-            updateIdleMaintenanceStateTm(false);   // resets 'started' and schedules next window
-            mWakeLock.release();
-        } else {
-            Slog.e(TAG, "ERROR: idle shutdown but invariants not held.  last=" + mLastIdler
-                    + " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size());
-        }
-    }
-
-    private long getNextIdleMaintenanceIntervalStartFromNow() {
-        return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
-                - SystemClock.elapsedRealtime();
-    }
-
-    private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
-        final int minBatteryLevel = isBatteryCharging()
-                ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
-                : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
-        boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
-                && mBatteryService.getBatteryLevel() > minBatteryLevel);
-        if (!allowed && noisy) {
-            Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power");
-        }
-        return allowed;
-    }
-
-    private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) {
-        // The last time the user poked the device is above the threshold.
-        boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
-                && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
-                    > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
-        if (!allowed && noisy) {
-            Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity");
-        }
-        return allowed;
-    }
-
-    private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) {
-        // Enough time passed since the last maintenance run.
-        boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
-                > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
-        if (!allowed && noisy) {
-            Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last");
-        }
-        return allowed;
-    }
-
-    private boolean lastUserActivityPermitsIdleMaintenanceRunning() {
-        // The user is not using the device.
-        return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID);
-    }
-
-    private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() {
-        // Battery not too low and the maintenance duration did not timeout.
-        return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING
-                && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION
-                        > SystemClock.elapsedRealtime());
-    }
-
-    private boolean isBatteryCharging() {
-        return mBatteryService.getPlugType() > 0
-                && mBatteryService.getInvalidCharger() == 0;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (DEBUG) {
-            Log.i(TAG, intent.getAction());
-        }
-        String action = intent.getAction();
-        if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-            // We care about battery only if maintenance is in progress so we can
-            // stop it if battery is too low. Note that here we assume that the
-            // maintenance clients are properly holding a wake lock. We will
-            // refactor the maintenance to use services instead of intents for the
-            // next release. The only client for this for now is internal an holds
-            // a wake lock correctly.
-            if (mIdleMaintenanceStarted) {
-                updateIdleMaintenanceStateTm(false);
-            }
-        } else if (Intent.ACTION_SCREEN_ON.equals(action)
-                || Intent.ACTION_DREAMING_STOPPED.equals(action)) {
-            mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
-            // Unschedule any future updates since we already know that maintenance
-            // cannot be performed since the user is back.
-            unscheduleUpdateIdleMaintenanceState();
-            // If the screen went on/stopped dreaming, we know the user is using the
-            // device which means that idle maintenance should be stopped if running.
-            updateIdleMaintenanceStateTm(false);
-        } else if (Intent.ACTION_SCREEN_OFF.equals(action)
-                || Intent.ACTION_DREAMING_STARTED.equals(action)) {
-            mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
-            // If screen went off/started dreaming, we may be able to start idle maintenance
-            // after the minimal user inactivity elapses. We schedule an alarm for when
-            // this timeout elapses since the device may go to sleep by then.
-            scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
-        } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
-            updateIdleMaintenanceStateTm(false);
-        } else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
-            long now = SystemClock.elapsedRealtime() - 1;
-            mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
-            mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
-            updateIdleMaintenanceStateTm(true);
-        }
-    }
-}
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..10382ba 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,14 +472,15 @@
         }
 
         // 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,
                   flpHardwareProvider.getLocationHardware(),
-                  com.android.internal.R.bool.config_enableFusedLocationOverlay,
-                  com.android.internal.R.string.config_fusedLocationProviderPackageName,
+                  com.android.internal.R.bool.config_enableHardwareFlpOverlay,
+                  com.android.internal.R.string.config_hardwareFlpPackageName,
                   com.android.internal.R.array.config_locationProviderPackageNames);
           if(fusedProxy == null) {
               Slog.e(TAG, "Unable to bind FusedProxy.");
@@ -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/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index bcb6e9e..215d92d 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -30,8 +30,7 @@
     private static final String TAG = "MountServiceIdler";
 
     private static ComponentName sIdleService =
-            new ComponentName(MountServiceIdler.class.getPackage().getName(),
-                    MountServiceIdler.class.getName());
+            new ComponentName("android", MountServiceIdler.class.getName());
 
     private static int MOUNT_JOB_ID = 808;
 
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 362061e..f9c7a78 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -41,6 +41,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
@@ -61,6 +62,8 @@
 import android.os.SystemProperties;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -144,6 +147,7 @@
         public static final int InterfaceClassActivity    = 613;
         public static final int InterfaceAddressChange    = 614;
         public static final int InterfaceDnsServerInfo    = 615;
+        public static final int RouteChange               = 616;
     }
 
     static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
@@ -237,13 +241,22 @@
         mThread = new Thread(mConnector, NETD_TAG);
 
         mDaemonHandler = new Handler(FgThread.get().getLooper());
-        mPhoneStateListener = new PhoneStateListener(mDaemonHandler.getLooper()) {
+
+        mPhoneStateListener = new PhoneStateListener(SubscriptionManager.DEFAULT_SUB_ID,
+                mDaemonHandler.getLooper()) {
+            @Override
             public void onDataConnectionRealTimeInfoChanged(
                     DataConnectionRealTimeInfo dcRtInfo) {
+                if (DBG) Slog.d(TAG, "onDataConnectionRealTimeInfoChanged: " + dcRtInfo);
                 notifyInterfaceClassActivity(ConnectivityManager.TYPE_MOBILE,
                         dcRtInfo.getDcPowerState(), dcRtInfo.getTime(), true);
             }
         };
+        TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm != null) {
+            tm.listen(mPhoneStateListener,
+                    PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO);
+        }
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
@@ -577,6 +590,28 @@
         }
     }
 
+    /**
+     * Notify our observers of a route change.
+     */
+    private void notifyRouteChange(String action, RouteInfo route) {
+        final int length = mObservers.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    if (action.equals("updated")) {
+                        mObservers.getBroadcastItem(i).routeUpdated(route);
+                    } else {
+                        mObservers.getBroadcastItem(i).routeRemoved(route);
+                    }
+                } catch (RemoteException e) {
+                } catch (RuntimeException e) {
+                }
+            }
+        } finally {
+            mObservers.finishBroadcast();
+        }
+    }
+
     //
     // Netd Callback handling
     //
@@ -719,6 +754,47 @@
                     }
                     return true;
                     // break;
+            case NetdResponseCode.RouteChange:
+                    /*
+                     * A route has been updated or removed.
+                     * Format: "NNN Route <updated|removed> <dst> [via <gateway] [dev <iface>]"
+                     */
+                    if (!cooked[1].equals("Route") || cooked.length < 6) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+
+                    String via = null;
+                    String dev = null;
+                    boolean valid = true;
+                    for (int i = 4; (i + 1) < cooked.length && valid; i += 2) {
+                        if (cooked[i].equals("dev")) {
+                            if (dev == null) {
+                                dev = cooked[i+1];
+                            } else {
+                                valid = false;  // Duplicate interface.
+                            }
+                        } else if (cooked[i].equals("via")) {
+                            if (via == null) {
+                                via = cooked[i+1];
+                            } else {
+                                valid = false;  // Duplicate gateway.
+                            }
+                        } else {
+                            valid = false;      // Unknown syntax.
+                        }
+                    }
+                    if (valid) {
+                        try {
+                            // InetAddress.parseNumericAddress(null) inexplicably returns ::1.
+                            InetAddress gateway = null;
+                            if (via != null) gateway = InetAddress.parseNumericAddress(via);
+                            RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
+                            notifyRouteChange(cooked[2], route);
+                            return true;
+                        } catch (IllegalArgumentException e) {}
+                    }
+                    throw new IllegalStateException(errorMessage);
+                    // break;
             default: break;
             }
             return false;
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 512ebc6..1b71518 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -23,6 +23,7 @@
 import android.net.INetworkScoreCache;
 import android.net.INetworkScoreService;
 import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
 import android.net.ScoredNetwork;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -165,12 +166,12 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
-        String currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
+        NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
         if (currentScorer == null) {
             writer.println("Scoring is disabled.");
             return;
         }
-        writer.println("Current scorer: " + currentScorer);
+        writer.println("Current scorer: " + currentScorer.mPackageName);
         writer.flush();
 
         for (INetworkScoreCache scoreCache : getScoreCaches()) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index cfaf016..1bd837b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -33,10 +33,13 @@
 import android.os.UserHandle;
 import android.telephony.CellLocation;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.CellInfo;
+import android.telephony.VoLteServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PreciseCallState;
@@ -65,8 +68,9 @@
  */
 class TelephonyRegistry extends ITelephonyRegistry.Stub {
     private static final String TAG = "TelephonyRegistry";
-    private static final boolean DBG = false;
-    private static final boolean DBG_LOC = false;
+    private static final boolean DBG = false; // STOPSHIP if true
+    private static final boolean DBG_LOC = false; // STOPSHIP if true
+    private static final boolean VDBG = false; // STOPSHIP if true
 
     private static class Record {
         String pkgForDebug;
@@ -79,9 +83,13 @@
 
         int events;
 
+        long subId;
+
+        boolean isLegacyApp;
+
         @Override
         public String toString() {
-            return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid +
+            return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid + " subId=" + subId +
                     " events=" + Integer.toHexString(events) + "}";
         }
     }
@@ -94,41 +102,47 @@
 
     private final IBatteryStats mBatteryStats;
 
-    private int mCallState = TelephonyManager.CALL_STATE_IDLE;
+    private int mNumPhones;
 
-    private String mCallIncomingNumber = "";
+    private int[] mCallState;
 
-    private ServiceState mServiceState = new ServiceState();
+    private String[] mCallIncomingNumber;
 
-    private SignalStrength mSignalStrength = new SignalStrength();
+    private ServiceState[] mServiceState;
 
-    private boolean mMessageWaiting = false;
+    private SignalStrength[] mSignalStrength;
 
-    private boolean mCallForwarding = false;
+    private boolean[] mMessageWaiting;
 
-    private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
+    private boolean[] mCallForwarding;
 
-    private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN;
+    private int[] mDataActivity;
 
-    private boolean mDataConnectionPossible = false;
+    private int[] mDataConnectionState;
 
-    private String mDataConnectionReason = "";
+    private boolean[] mDataConnectionPossible;
 
-    private String mDataConnectionApn = "";
+    private String[] mDataConnectionReason;
+
+    private String[] mDataConnectionApn;
 
     private ArrayList<String> mConnectedApns;
 
-    private LinkProperties mDataConnectionLinkProperties;
+    private LinkProperties[] mDataConnectionLinkProperties;
 
-    private NetworkCapabilities mDataConnectionNetworkCapabilities;
+    private NetworkCapabilities[] mDataConnectionNetworkCapabilities;
 
-    private Bundle mCellLocation = new Bundle();
+    private Bundle[] mCellLocation;
 
-    private int mDataConnectionNetworkType;
+    private int[] mDataConnectionNetworkType;
 
     private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
 
-    private List<CellInfo> mCellInfo = null;
+    private ArrayList<List<CellInfo>> mCellInfo = null;
+
+    private VoLteServiceState mVoLteServiceState = new VoLteServiceState();
+
+    private long mDefaultSubId;
 
     private DataConnectionRealTimeInfo mDcRtInfo = new DataConnectionRealTimeInfo();
 
@@ -148,21 +162,40 @@
                 PhoneStateListener.LISTEN_CALL_STATE |
                 PhoneStateListener.LISTEN_DATA_ACTIVITY |
                 PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
-                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR;
+                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
+                PhoneStateListener.LISTEN_VOLTE_STATE;;
 
     static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
                 PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
 
     private static final int MSG_USER_SWITCHED = 1;
+    private static final int MSG_UPDATE_DEFAULT_SUB = 2;
 
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_USER_SWITCHED: {
-                    if (DBG) Slog.d(TAG, "MSG_USER_SWITCHED userId=" + msg.arg1);
-                    TelephonyRegistry.this.notifyCellLocation(mCellLocation);
+                    Slog.d(TAG, "MSG_USER_SWITCHED userId=" + msg.arg1);
+                    int numPhones = TelephonyManager.getDefault().getPhoneCount();
+                    for (int sub = 0; sub < numPhones; sub++) {
+                        TelephonyRegistry.this.notifyCellLocationUsingSubId(sub, mCellLocation[sub]);
+                    }
+                    break;
+                }
+                case MSG_UPDATE_DEFAULT_SUB: {
+                    Slog.d(TAG, "MSG_UPDATE_DEFAULT_SUB subid=" + mDefaultSubId);
+                    // Default subscription id changed, update the changed default subscription
+                    // id in  all the legacy application listener records.
+                    synchronized (mRecords) {
+                        for (Record r : mRecords) {
+                            // FIXME: Be sure we're using isLegacyApp correctly!
+                            if (r.isLegacyApp == true) {
+                                r.subId = mDefaultSubId;
+                            }
+                        }
+                    }
                     break;
                 }
             }
@@ -173,9 +206,16 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
+            Slog.d(TAG, "mBroadcastReceiver: action=" + action);
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
-                       intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+                int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                if (DBG) Slog.d(TAG, "onReceive: userHandle=" + userHandle);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, userHandle, 0));
+            } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)) {
+                mDefaultSubId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                        SubscriptionManager.getDefaultSubId());
+                if (DBG) Slog.d(TAG, "onReceive: mDefaultSubId=" + mDefaultSubId);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_DEFAULT_SUB, 0, 0));
             }
         }
     };
@@ -190,13 +230,55 @@
     TelephonyRegistry(Context context) {
         CellLocation  location = CellLocation.getEmpty();
 
+        mContext = context;
+        mBatteryStats = BatteryStatsService.getService();
+        mConnectedApns = new ArrayList<String>();
+
+        // Initialize default subscription to be used for single standby.
+        mDefaultSubId = SubscriptionManager.getDefaultSubId();
+
+        int numPhones = TelephonyManager.getDefault().getPhoneCount();
+        if (DBG) Slog.d(TAG, "TelephonyRegistor: ctor numPhones=" + numPhones);
+        mNumPhones = numPhones;
+        mCallState = new int[numPhones];
+        mDataActivity = new int[numPhones];
+        mDataConnectionState = new int[numPhones];
+        mDataConnectionNetworkType = new int[numPhones];
+        mCallIncomingNumber = new String[numPhones];
+        mServiceState = new ServiceState[numPhones];
+        mSignalStrength = new SignalStrength[numPhones];
+        mMessageWaiting = new boolean[numPhones];
+        mDataConnectionPossible = new boolean[numPhones];
+        mDataConnectionReason = new String[numPhones];
+        mDataConnectionApn = new String[numPhones];
+        mCallForwarding = new boolean[numPhones];
+        mCellLocation = new Bundle[numPhones];
+        mDataConnectionLinkProperties = new LinkProperties[numPhones];
+        mDataConnectionNetworkCapabilities = new NetworkCapabilities[numPhones];
+        mCellInfo = new ArrayList<List<CellInfo>>();
+        for (int i = 0; i < numPhones; i++) {
+            mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
+            mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
+            mDataConnectionState[i] = TelephonyManager.DATA_UNKNOWN;
+            mCallIncomingNumber[i] =  "";
+            mServiceState[i] =  new ServiceState();
+            mSignalStrength[i] =  new SignalStrength();
+            mMessageWaiting[i] =  false;
+            mCallForwarding[i] =  false;
+            mDataConnectionPossible[i] = false;
+            mDataConnectionReason[i] =  "";
+            mDataConnectionApn[i] =  "";
+            mCellLocation[i] = new Bundle();
+            mCellInfo.add(i, null);
+        }
+
         // Note that location can be null for non-phone builds like
         // like the generic one.
         if (location != null) {
-            location.fillInNotifierBundle(mCellLocation);
+            for (int i = 0; i < numPhones; i++) {
+                location.fillInNotifierBundle(mCellLocation[i]);
+            }
         }
-        mContext = context;
-        mBatteryStats = BatteryStatsService.getService();
         mConnectedApns = new ArrayList<String>();
     }
 
@@ -205,16 +287,31 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
+        filter.addAction(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+        Slog.d(TAG, "systemRunning register for intents");
         mContext.registerReceiver(mBroadcastReceiver, filter);
     }
 
     @Override
     public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
             boolean notifyNow) {
+        listen(pkgForDebug, callback, events, notifyNow, mDefaultSubId, true);
+    }
+
+    @Override
+    public void listenUsingSubId(long subId, String pkgForDebug, IPhoneStateListener callback,
+            int events, boolean notifyNow) {
+        listen(pkgForDebug, callback, events, notifyNow, subId, false);
+    }
+
+    private void listen(String pkgForDebug, IPhoneStateListener callback, int events,
+            boolean notifyNow, long subId, boolean isLegacyApp) {
         int callerUid = UserHandle.getCallingUserId();
         int myUid = UserHandle.myUserId();
-        if (DBG) {
+        if (VDBG) {
             Slog.d(TAG, "listen: E pkg=" + pkgForDebug + " events=0x" + Integer.toHexString(events)
+                + " notifyNow=" + notifyNow + " subId=" + subId
+                + " isLegacyApp=" + isLegacyApp
                 + " myUid=" + myUid
                 + " callerUid=" + callerUid);
         }
@@ -239,22 +336,38 @@
                     r.callback = callback;
                     r.pkgForDebug = pkgForDebug;
                     r.callerUid = callerUid;
+                    r.subId = subId;
+                    r.isLegacyApp = isLegacyApp;
+                    // Legacy applications pass invalid subId(-1), based on
+                    // the received subId value update the isLegacyApp field
+                    if ((r.subId <= 0) || (r.subId == SubscriptionManager.INVALID_SUB_ID)) {
+                        r.subId = mDefaultSubId;
+                        r.isLegacyApp = true; // r.subId is to be update when default changes.
+                    }
+                    if (r.subId == SubscriptionManager.DEFAULT_SUB_ID) {
+                        r.subId = mDefaultSubId;
+                        r.isLegacyApp = true; // r.subId is to be update when default changes.
+                        if (DBG) Slog.i(TAG, "listen: DEFAULT_SUB_ID");
+                    }
                     mRecords.add(r);
-                    if (DBG) Slog.i(TAG, "listen: add new record=" + r);
+                    if (DBG) Slog.i(TAG, "listen: add new record");
                 }
-                int send = events & (events ^ r.events);
+                int phoneId = SubscriptionManager.getPhoneId(subId);
                 r.events = events;
-                if (notifyNow) {
+                if (DBG) Slog.i(TAG, "listen: set events record=" + r);
+                if (notifyNow && validatePhoneId(phoneId)) {
                     if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
                         try {
-                            r.callback.onServiceStateChanged(new ServiceState(mServiceState));
+                            r.callback.onServiceStateChanged(
+                                    new ServiceState(mServiceState[phoneId]));
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
                         try {
-                            int gsmSignalStrength = mSignalStrength.getGsmSignalStrength();
+                            int gsmSignalStrength = mSignalStrength[phoneId]
+                                    .getGsmSignalStrength();
                             r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
                                     : gsmSignalStrength));
                         } catch (RemoteException ex) {
@@ -263,51 +376,56 @@
                     }
                     if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
                         try {
-                            r.callback.onMessageWaitingIndicatorChanged(mMessageWaiting);
+                            r.callback.onMessageWaitingIndicatorChanged(
+                                    mMessageWaiting[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
                         try {
-                            r.callback.onCallForwardingIndicatorChanged(mCallForwarding);
+                            r.callback.onCallForwardingIndicatorChanged(
+                                    mCallForwarding[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
                         try {
-                            if (DBG_LOC) Slog.d(TAG, "listen: mCellLocation=" + mCellLocation);
-                            r.callback.onCellLocationChanged(new Bundle(mCellLocation));
+                            if (DBG_LOC) Slog.d(TAG, "listen: mCellLocation = "
+                                    + mCellLocation[phoneId]);
+                            r.callback.onCellLocationChanged(
+                                    new Bundle(mCellLocation[phoneId]));
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
                         try {
-                            r.callback.onCallStateChanged(mCallState, mCallIncomingNumber);
+                            r.callback.onCallStateChanged(mCallState[phoneId],
+                                     mCallIncomingNumber[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
                         try {
-                            r.callback.onDataConnectionStateChanged(mDataConnectionState,
-                                mDataConnectionNetworkType);
+                            r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+                                mDataConnectionNetworkType[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
                         try {
-                            r.callback.onDataActivity(mDataActivity);
+                            r.callback.onDataActivity(mDataActivity[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
                     }
                     if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
                         try {
-                            r.callback.onSignalStrengthsChanged(mSignalStrength);
+                            r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -321,8 +439,9 @@
                     }
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
                         try {
-                            if (DBG_LOC) Slog.d(TAG, "listen: mCellInfo=" + mCellInfo);
-                            r.callback.onCellInfoChanged(mCellInfo);
+                            if (DBG_LOC) Slog.d(TAG, "listen: mCellInfo[" + phoneId + "] = "
+                                    + mCellInfo.get(phoneId));
+                            r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -373,10 +492,10 @@
             return;
         }
         synchronized (mRecords) {
-            mCallState = state;
-            mCallIncomingNumber = incomingNumber;
             for (Record r : mRecords) {
-                if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
+                if (((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) &&
+                    (r.isLegacyApp == true)) {
+                    // FIXME: why does isLegacyApp need to be true?
                     try {
                         r.callback.onCallStateChanged(state, incomingNumber);
                     } catch (RemoteException ex) {
@@ -386,82 +505,148 @@
             }
             handleRemoveListLocked();
         }
-        broadcastCallStateChanged(state, incomingNumber);
+        broadcastCallStateChanged(state, incomingNumber, mDefaultSubId);
     }
 
-    public void notifyServiceState(ServiceState state) {
+    public void notifyCallStateUsingSubId(long subId, int state, String incomingNumber) {
+        if (!checkNotifyPermission("notifyCallState()")) {
+            return;
+        }
+        if (VDBG) {
+            Slog.d(TAG, "notifyCallStateUsingSubId: subId=" + subId
+                + " state=" + state + " incomingNumber=" + incomingNumber);
+        }
+        synchronized (mRecords) {
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mCallState[phoneId] = state;
+                mCallIncomingNumber[phoneId] = incomingNumber;
+                for (Record r : mRecords) {
+                    if (((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) &&
+                        (r.subId == subId) && (r.isLegacyApp == false)) {
+                        // FIXME: why isLegacyApp false?
+                        try {
+                            r.callback.onCallStateChanged(state, incomingNumber);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+        broadcastCallStateChanged(state, incomingNumber, subId);
+    }
+
+     public void notifyServiceState(ServiceState state) {
+         notifyServiceStateUsingSubId(mDefaultSubId, state);
+     }
+
+    public void notifyServiceStateUsingSubId(long subId, ServiceState state) {
         if (!checkNotifyPermission("notifyServiceState()")){
             return;
         }
-        long ident = Binder.clearCallingIdentity();
-        try {
-            mBatteryStats.notePhoneState(state.getState());
-        } catch (RemoteException re) {
-            // Can't do much
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+        if (subId == SubscriptionManager.DEFAULT_SUB_ID) {
+            subId = mDefaultSubId;
+            Slog.d(TAG, "notifyServiceStateUsingSubId: using mDefaultSubId=" + mDefaultSubId);
+        }
+        if (VDBG) {
+            Slog.d(TAG, "notifyServiceStateUsingSubId: subId=" + subId
+                + " state=" + state);
         }
         synchronized (mRecords) {
-            mServiceState = state;
-            for (Record r : mRecords) {
-                if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
-                    try {
-                        r.callback.onServiceStateChanged(new ServiceState(state));
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mServiceState[phoneId] = state;
+                for (Record r : mRecords) {
+                    // FIXME: use DEFAULT_SUB_ID instead??
+                    if (((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) &&
+                            (r.subId == subId)) {
+                        try {
+                            r.callback.onServiceStateChanged(new ServiceState(state));
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
                 }
+            } else {
+                Slog.d(TAG, "notifyServiceStateUsingSubId: INVALID phoneId=" + phoneId);
             }
             handleRemoveListLocked();
         }
-        broadcastServiceStateChanged(state);
+        broadcastServiceStateChanged(state, subId);
     }
 
     public void notifySignalStrength(SignalStrength signalStrength) {
+        notifySignalStrengthUsingSubId(mDefaultSubId, signalStrength);
+    }
+
+    public void notifySignalStrengthUsingSubId(long subId, SignalStrength signalStrength) {
         if (!checkNotifyPermission("notifySignalStrength()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifySignalStrengthUsingSubId: subId=" + subId
+                + " signalStrength=" + signalStrength);
+        }
         synchronized (mRecords) {
-            mSignalStrength = signalStrength;
-            for (Record r : mRecords) {
-                if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
-                    try {
-                        r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mSignalStrength[phoneId] = signalStrength;
+                for (Record r : mRecords) {
+                    if (((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) &&
+                        (r.subId == subId)){
+                        try {
+                            r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
-                }
-                if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
-                    try {
-                        int gsmSignalStrength = signalStrength.getGsmSignalStrength();
-                        r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
-                                : gsmSignalStrength));
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+                    if (((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) &&
+                        (r.subId == subId)) {
+                        try {
+                            int gsmSignalStrength = signalStrength.getGsmSignalStrength();
+                            r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+                                    : gsmSignalStrength));
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
                 }
             }
             handleRemoveListLocked();
         }
-        broadcastSignalStrengthChanged(signalStrength);
+        broadcastSignalStrengthChanged(signalStrength, subId);
     }
 
     public void notifyCellInfo(List<CellInfo> cellInfo) {
+         notifyCellInfoUsingSubId(mDefaultSubId, cellInfo);
+    }
+
+    public void notifyCellInfoUsingSubId(long subId, List<CellInfo> cellInfo) {
         if (!checkNotifyPermission("notifyCellInfo()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifyCellInfoUsingSubId: subId=" + subId
+                + " cellInfo=" + cellInfo);
+        }
 
         synchronized (mRecords) {
-            mCellInfo = cellInfo;
-            for (Record r : mRecords) {
-                if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
-                    try {
-                        if (DBG_LOC) {
-                            Slog.d(TAG, "notifyCellInfo: mCellInfo=" + mCellInfo + " r=" + r);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mCellInfo.set(phoneId, cellInfo);
+                for (Record r : mRecords) {
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)
+                            && r.subId == subId) {
+                        try {
+                            if (DBG_LOC) {
+                                Slog.d(TAG, "notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r);
+                            }
+                            r.callback.onCellInfoChanged(cellInfo);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
                         }
-                        r.callback.onCellInfoChanged(cellInfo);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
                     }
                 }
             }
@@ -495,17 +680,29 @@
     }
 
     public void notifyMessageWaitingChanged(boolean mwi) {
+        notifyMessageWaitingChangedUsingSubId(mDefaultSubId, mwi);
+    }
+
+    public void notifyMessageWaitingChangedUsingSubId(long subId, boolean mwi) {
         if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifyMessageWaitingChangedUsingSubId: subId=" + subId
+                + " mwi=" + mwi);
+        }
         synchronized (mRecords) {
-            mMessageWaiting = mwi;
-            for (Record r : mRecords) {
-                if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
-                    try {
-                        r.callback.onMessageWaitingIndicatorChanged(mwi);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mMessageWaiting[phoneId] = mwi;
+                for (Record r : mRecords) {
+                    if (((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) &&
+                        (r.subId == subId)) {
+                        try {
+                            r.callback.onMessageWaitingIndicatorChanged(mwi);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
                 }
             }
@@ -514,17 +711,29 @@
     }
 
     public void notifyCallForwardingChanged(boolean cfi) {
+        notifyCallForwardingChangedUsingSubId(mDefaultSubId, cfi);
+    }
+
+    public void notifyCallForwardingChangedUsingSubId(long subId, boolean cfi) {
         if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifyCallForwardingChangedUsingSubId: subId=" + subId
+                + " cfi=" + cfi);
+        }
         synchronized (mRecords) {
-            mCallForwarding = cfi;
-            for (Record r : mRecords) {
-                if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
-                    try {
-                        r.callback.onCallForwardingIndicatorChanged(cfi);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mCallForwarding[phoneId] = cfi;
+                for (Record r : mRecords) {
+                    if (((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) &&
+                        (r.subId == subId)) {
+                        try {
+                            r.callback.onCallForwardingIndicatorChanged(cfi);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
                 }
             }
@@ -533,11 +742,16 @@
     }
 
     public void notifyDataActivity(int state) {
+        notifyDataActivityUsingSubId(mDefaultSubId, state);
+    }
+
+    public void notifyDataActivityUsingSubId(long subId, int state) {
         if (!checkNotifyPermission("notifyDataActivity()" )) {
             return;
         }
         synchronized (mRecords) {
-            mDataActivity = state;
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            mDataActivity[phoneId] = state;
             for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
                     try {
@@ -554,29 +768,40 @@
     public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
             String reason, String apn, String apnType, LinkProperties linkProperties,
             NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+        notifyDataConnectionUsingSubId(mDefaultSubId, state, isDataConnectivityPossible,
+            reason, apn, apnType, linkProperties,
+            networkCapabilities, networkType, roaming);
+    }
+
+    public void notifyDataConnectionUsingSubId(long subId, int state,
+            boolean isDataConnectivityPossible, String reason, String apn, String apnType,
+            LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
+            int networkType, boolean roaming) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
-        if (DBG) {
-            Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible="
-                + isDataConnectivityPossible + " reason='" + reason
+        if (VDBG) {
+            Slog.i(TAG, "notifyDataConnectionUsingSubId: subId=" + subId
+                + " state=" + state + " isDataConnectivityPossible=" + isDataConnectivityPossible
+                + " reason='" + reason
                 + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
                 + " mRecords.size()=" + mRecords.size() + " mRecords=" + mRecords);
         }
         synchronized (mRecords) {
+            int phoneId = SubscriptionManager.getPhoneId(subId);
             boolean modified = false;
             if (state == TelephonyManager.DATA_CONNECTED) {
                 if (!mConnectedApns.contains(apnType)) {
                     mConnectedApns.add(apnType);
-                    if (mDataConnectionState != state) {
-                        mDataConnectionState = state;
+                    if (mDataConnectionState[phoneId] != state) {
+                        mDataConnectionState[phoneId] = state;
                         modified = true;
                     }
                 }
             } else {
                 if (mConnectedApns.remove(apnType)) {
                     if (mConnectedApns.isEmpty()) {
-                        mDataConnectionState = state;
+                        mDataConnectionState[phoneId] = state;
                         modified = true;
                     } else {
                         // leave mDataConnectionState as is and
@@ -584,25 +809,28 @@
                     }
                 }
             }
-            mDataConnectionPossible = isDataConnectivityPossible;
-            mDataConnectionReason = reason;
-            mDataConnectionLinkProperties = linkProperties;
-            mDataConnectionNetworkCapabilities = networkCapabilities;
-            if (mDataConnectionNetworkType != networkType) {
-                mDataConnectionNetworkType = networkType;
+            mDataConnectionPossible[phoneId] = isDataConnectivityPossible;
+            mDataConnectionReason[phoneId] = reason;
+            mDataConnectionLinkProperties[phoneId] = linkProperties;
+            mDataConnectionNetworkCapabilities[phoneId] = networkCapabilities;
+            if (mDataConnectionNetworkType[phoneId] != networkType) {
+                mDataConnectionNetworkType[phoneId] = networkType;
                 // need to tell registered listeners about the new network type
                 modified = true;
             }
             if (modified) {
                 if (DBG) {
-                    Slog.d(TAG, "onDataConnectionStateChanged(" + mDataConnectionState
-                        + ", " + mDataConnectionNetworkType + ")");
+                    Slog.d(TAG, "onDataConnectionStateChanged(" + mDataConnectionState[phoneId]
+                        + ", " + mDataConnectionNetworkType[phoneId] + ")");
                 }
                 for (Record r : mRecords) {
-                    if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+                    if (((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) &&
+                            (r.subId == subId)) {
                         try {
-                            r.callback.onDataConnectionStateChanged(mDataConnectionState,
-                                    mDataConnectionNetworkType);
+                            Slog.d(TAG,"Notify data connection state changed on sub: " +
+                                    subId);
+                            r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+                                    mDataConnectionNetworkType[phoneId]);
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -624,15 +852,24 @@
             handleRemoveListLocked();
         }
         broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
-                apnType, linkProperties, networkCapabilities, roaming);
+                apnType, linkProperties, networkCapabilities, roaming, subId);
         broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, reason,
                 linkProperties, "");
     }
 
     public void notifyDataConnectionFailed(String reason, String apnType) {
+         notifyDataConnectionFailedUsingSubId(mDefaultSubId, reason, apnType);
+    }
+
+    public void notifyDataConnectionFailedUsingSubId(long subId,
+            String reason, String apnType) {
         if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifyDataConnectionFailedUsingSubId: subId=" + subId
+                + " reason=" + reason + " apnType=" + apnType);
+        }
         synchronized (mRecords) {
             mPreciseDataConnectionState = new PreciseDataConnectionState(
                     TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -648,29 +885,42 @@
             }
             handleRemoveListLocked();
         }
-        broadcastDataConnectionFailed(reason, apnType);
+        broadcastDataConnectionFailed(reason, apnType, subId);
         broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", reason, null, "");
     }
 
     public void notifyCellLocation(Bundle cellLocation) {
+         notifyCellLocationUsingSubId(mDefaultSubId, cellLocation);
+    }
+
+    public void notifyCellLocationUsingSubId(long subId, Bundle cellLocation) {
+        Slog.d(TAG, "notifyCellLocationUsingSubId: subId=" + subId
+                + " cellLocation=" + cellLocation);
         if (!checkNotifyPermission("notifyCellLocation()")) {
             return;
         }
+        if (VDBG) {
+            Slog.d(TAG, "notifyCellLocationUsingSubId: subId=" + subId
+                + " cellLocation=" + cellLocation);
+        }
         synchronized (mRecords) {
-            mCellLocation = cellLocation;
-            for (Record r : mRecords) {
-                if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
-                    try {
-                        if (DBG_LOC) {
-                            Slog.d(TAG, "notifyCellLocation: mCellLocation=" + mCellLocation
-                                    + " r=" + r);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (validatePhoneId(phoneId)) {
+                mCellLocation[phoneId] = cellLocation;
+                for (Record r : mRecords) {
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)
+                            && r.subId == subId) {
+                        try {
+                            if (DBG_LOC) {
+                                Slog.d(TAG, "notifyCellLocation: cellLocation=" + cellLocation
+                                        + " r=" + r);
+                            }
+                            r.callback.onCellLocationChanged(new Bundle(cellLocation));
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
                         }
-                        r.callback.onCellLocationChanged(new Bundle(cellLocation));
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
                     }
-
                 }
             }
             handleRemoveListLocked();
@@ -771,6 +1021,26 @@
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, reason, null, failCause);
     }
 
+    public void notifyVoLteServiceStateChanged(VoLteServiceState lteState) {
+        if (!checkNotifyPermission("notifyVoLteServiceStateChanged()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mVoLteServiceState = lteState;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_VOLTE_STATE) != 0) {
+                    try {
+                        r.callback.onVoLteServiceStateChanged(
+                                new VoLteServiceState(mVoLteServiceState));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -796,6 +1066,7 @@
             pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
             pw.println("  mDataConnectionNetworkCapabilities=" +
                     mDataConnectionNetworkCapabilities);
+            pw.println("  mDefaultSubId=" + mDefaultSubId);
             pw.println("  mCellLocation=" + mCellLocation);
             pw.println("  mCellInfo=" + mCellInfo);
             pw.println("  mDcRtInfo=" + mDcRtInfo);
@@ -810,15 +1081,26 @@
     // the legacy intent broadcasting
     //
 
-    private void broadcastServiceStateChanged(ServiceState state) {
+    private void broadcastServiceStateChanged(ServiceState state, long subId) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.notePhoneState(state.getState());
+        } catch (RemoteException re) {
+            // Can't do much
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         Bundle data = new Bundle();
         state.fillInNotifierBundle(data);
         intent.putExtras(data);
+        // Pass the subscription along with the intent.
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void broadcastSignalStrengthChanged(SignalStrength signalStrength) {
+    private void broadcastSignalStrengthChanged(SignalStrength signalStrength, long subId) {
         long ident = Binder.clearCallingIdentity();
         try {
             mBatteryStats.notePhoneSignalStrength(signalStrength);
@@ -833,10 +1115,11 @@
         Bundle data = new Bundle();
         signalStrength.fillInNotifierBundle(data);
         intent.putExtras(data);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void broadcastCallStateChanged(int state, String incomingNumber) {
+    private void broadcastCallStateChanged(int state, String incomingNumber, long subId) {
         long ident = Binder.clearCallingIdentity();
         try {
             if (state == TelephonyManager.CALL_STATE_IDLE) {
@@ -856,6 +1139,7 @@
         if (!TextUtils.isEmpty(incomingNumber)) {
             intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
         }
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                 android.Manifest.permission.READ_PHONE_STATE);
     }
@@ -863,7 +1147,7 @@
     private void broadcastDataConnectionStateChanged(int state,
             boolean isDataConnectivityPossible,
             String reason, String apn, String apnType, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities, boolean roaming) {
+            NetworkCapabilities networkCapabilities, boolean roaming, long subId) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
@@ -890,13 +1174,16 @@
 
         intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
         intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void broadcastDataConnectionFailed(String reason, String apnType) {
+    private void broadcastDataConnectionFailed(String reason, String apnType,
+            long subId) {
         Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
         intent.putExtra(PhoneConstants.FAILURE_REASON_KEY, reason);
         intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -989,4 +1276,10 @@
         }
         return valid;
     }
+
+    private boolean validatePhoneId(int phoneId) {
+        boolean valid = (phoneId >= 0) && (phoneId < mNumPhones);
+        if (VDBG) Slog.d(TAG, "validatePhoneId: " + valid);
+        return valid;
+    }
 }
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index b2aaf74..e152ebe 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -537,13 +537,16 @@
     }
 
     @Override
-    public AuthenticatorDescription[] getAuthenticatorTypes() {
+    public AuthenticatorDescription[] getAuthenticatorTypes(int userId) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getAuthenticatorTypes: "
+                    + "for user id " + userId
                     + "caller's uid " + Binder.getCallingUid()
                     + ", pid " + Binder.getCallingPid());
         }
-        final int userId = UserHandle.getCallingUserId();
+        // Only allow the system process to read accounts of other users
+        enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+                + " trying get authenticator types for " + userId);
         final long identityToken = clearCallingIdentity();
         try {
             Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
@@ -562,6 +565,16 @@
         }
     }
 
+    private void enforceCrossUserPermission(int userId, String errorMessage) {
+        if (userId != UserHandle.getCallingUserId()
+                && Binder.getCallingUid() != Process.myUid()
+                && mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(errorMessage);
+        }
+    }
+
     @Override
     public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1567,14 +1580,8 @@
             final Account account, final Bundle options, final boolean expectActivityLaunch,
             int userId) {
         // Only allow the system process to read accounts of other users
-        if (userId != UserHandle.getCallingUserId()
-                && Binder.getCallingUid() != Process.myUid()
-                && mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                    != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("User " + UserHandle.getCallingUserId()
+        enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
                     + " trying to confirm account credentials for " + userId);
-        }
 
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "confirmCredentials: " + account
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 108a079..69262c8 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);
                     }
@@ -1327,7 +1328,7 @@
                         + " app=" + app);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(r.appInfo.packageName, mAm.mProcessStats);
+                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                     realStartServiceLocked(r, app, execInFg);
                     return null;
                 } catch (RemoteException e) {
@@ -1882,7 +1883,8 @@
 
                     mPendingServices.remove(i);
                     i--;
-                    proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats);
+                    proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
+                            mAm.mProcessStats);
                     realStartServiceLocked(sr, proc, sr.createdFromFg);
                     didSomething = true;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd1baac..c2523be 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;
@@ -35,6 +36,9 @@
 import android.app.IAppTask;
 import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManager;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
 import android.graphics.Rect;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
@@ -170,8 +174,12 @@
 import android.os.UpdateLock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
 import android.util.AtomicFile;
 import android.util.EventLog;
 import android.util.Log;
@@ -185,6 +193,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.TextView;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -283,7 +292,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;
@@ -569,6 +578,12 @@
     long mLastFullPssTime = SystemClock.uptimeMillis();
 
     /**
+     * If set, the next time we collect PSS data we should do a full collection
+     * with data from native processes and the kernel.
+     */
+    boolean mFullPssPending = false;
+
+    /**
      * This is the process holding what we currently consider to be
      * the "home" activity.
      */
@@ -1157,6 +1172,8 @@
     CompatModeDialog mCompatModeDialog;
     long mLastMemUsageReportTime = 0;
 
+    private LockToAppRequestDialog mLockToAppRequest;
+
     /**
      * Flag whether the current user is a "monkey", i.e. whether
      * the UI is driven by a UI automation tool.
@@ -1800,8 +1817,47 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case COLLECT_PSS_BG_MSG: {
-                int i=0, num=0;
                 long start = SystemClock.uptimeMillis();
+                MemInfoReader memInfo = null;
+                synchronized (ActivityManagerService.this) {
+                    if (mFullPssPending) {
+                        mFullPssPending = false;
+                        memInfo = new MemInfoReader();
+                    }
+                }
+                if (memInfo != null) {
+                    updateCpuStatsNow();
+                    long nativeTotalPss = 0;
+                    synchronized (mProcessCpuThread) {
+                        final int N = mProcessCpuTracker.countStats();
+                        for (int j=0; j<N; j++) {
+                            ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j);
+                            if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID) {
+                                // This is definitely an application process; skip it.
+                                continue;
+                            }
+                            synchronized (mPidsSelfLocked) {
+                                if (mPidsSelfLocked.indexOfKey(st.pid) >= 0) {
+                                    // This is one of our own processes; skip it.
+                                    continue;
+                                }
+                            }
+                            nativeTotalPss += Debug.getPss(st.pid, null);
+                        }
+                    }
+                    memInfo.readMemInfo();
+                    synchronized (this) {
+                        if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+                                + (SystemClock.uptimeMillis()-start) + "ms");
+                        mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                                memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
+                                memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()
+                                        +memInfo.getSlabSizeKb(),
+                                nativeTotalPss);
+                    }
+                }
+
+                int i=0, num=0;
                 long[] tmp = new long[1];
                 do {
                     ProcessRecord proc;
@@ -2175,6 +2231,8 @@
             }
         };
 
+        mLockToAppRequest = new LockToAppRequestDialog(mContext, this);
+
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
     }
@@ -2189,10 +2247,15 @@
         mBatteryStatsService.publish(mContext);
         mUsageStatsService.publish(mContext);
         mAppOpsService.publish(mContext);
-
+        Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, new LocalService());
     }
 
+    public void initPowerManagement() {
+        mStackSupervisor.initPowerManagement();
+        mBatteryStatsService.initPowerManagement();
+    }
+
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -2763,7 +2826,7 @@
                 // come up (we have a pid but not yet its thread), so keep it.
                 if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
                 // If this is a new package in the process, add the package to the list
-                app.addPackage(info.packageName, mProcessStats);
+                app.addPackage(info.packageName, info.versionCode, mProcessStats);
                 return app;
             }
 
@@ -2818,7 +2881,7 @@
             }
         } else {
             // If this is a new package in the process, add the package to the list
-            app.addPackage(info.packageName, mProcessStats);
+            app.addPackage(info.packageName, info.versionCode, mProcessStats);
         }
 
         // If the system is not ready yet, then hold off on starting this
@@ -2833,7 +2896,7 @@
             return app;
         }
 
-        startProcessLocked(app, hostingType, hostingNameStr);
+        startProcessLocked(app, hostingType, hostingNameStr, null /* ABI override */);
         return (app.pid != 0) ? app : null;
     }
 
@@ -2842,7 +2905,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 +2945,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 +2991,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 +3041,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;
@@ -3651,6 +3719,12 @@
             // Keep track of the root activity of the task before we finish it
             TaskRecord tr = r.task;
             ActivityRecord rootR = tr.getRootActivity();
+            // Do not allow task to finish in Lock Task mode.
+            if (tr == mStackSupervisor.mLockTaskModeTask) {
+                if (rootR == r) {
+                    return false;
+                }
+            }
             if (mController != null) {
                 // Find the first activity that is not finishing.
                 ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
@@ -3795,13 +3869,25 @@
     public boolean finishActivityAffinity(IBinder token) {
         synchronized(this) {
             final long origId = Binder.clearCallingIdentity();
-            ActivityRecord r = ActivityRecord.isInStackLocked(token);
-            boolean res = false;
-            if (r != null) {
-                res = r.task.stack.finishActivityAffinityLocked(r);
+            try {
+                ActivityRecord r = ActivityRecord.isInStackLocked(token);
+
+                ActivityRecord rootR = r.task.getRootActivity();
+                // Do not allow task to finish in Lock Task mode.
+                if (r.task == mStackSupervisor.mLockTaskModeTask) {
+                    if (rootR == r) {
+                        Binder.restoreCallingIdentity(origId);
+                        return false;
+                    }
+                }
+                boolean res = false;
+                if (r != null) {
+                    res = r.task.stack.finishActivityAffinityLocked(r);
+                }
+                return res;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            Binder.restoreCallingIdentity(origId);
-            return res;
         }
     }
 
@@ -5033,7 +5119,7 @@
 
             if (app.persistent && !app.isolated) {
                 if (!callerWillRestart) {
-                    addAppLocked(app.info, false);
+                    addAppLocked(app.info, false, null /* ABI override */);
                 } else {
                     needRestart = true;
                 }
@@ -5146,7 +5232,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 +5325,7 @@
 
             app.resetPackageList(mProcessStats);
             app.unlinkDeathRecipient();
-            startProcessLocked(app, "bind fail", processName);
+            startProcessLocked(app, "bind fail", processName, null /* ABI override */);
             return false;
         }
 
@@ -5390,7 +5476,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 */);
                 }
             }
             
@@ -6033,7 +6119,11 @@
         if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
             return false;
         }
+        return checkHoldingPermissionsInternalLocked(pm, pi, grantUri, uid, modeFlags, true);
+    }
 
+    private final boolean checkHoldingPermissionsInternalLocked(IPackageManager pm, ProviderInfo pi,
+            GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
         if (pi.applicationInfo.uid == uid) {
             return true;
         } else if (!pi.exported) {
@@ -6044,11 +6134,11 @@
         boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
         try {
             // check if target holds top-level <provider> permissions
-            if (!readMet && pi.readPermission != null
+            if (!readMet && pi.readPermission != null && considerUidPermissions
                     && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
                 readMet = true;
             }
-            if (!writeMet && pi.writePermission != null
+            if (!writeMet && pi.writePermission != null && considerUidPermissions
                     && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
                 writeMet = true;
             }
@@ -6074,7 +6164,8 @@
                                     + ": match=" + pp.match(path)
                                     + " check=" + pm.checkUidPermission(pprperm, uid));
                             if (pprperm != null) {
-                                if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) {
+                                if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
+                                        == PERMISSION_GRANTED) {
                                     readMet = true;
                                 } else {
                                     allowDefaultRead = false;
@@ -6088,7 +6179,8 @@
                                     + ": match=" + pp.match(path)
                                     + " check=" + pm.checkUidPermission(ppwperm, uid));
                             if (ppwperm != null) {
-                                if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) {
+                                if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
+                                        == PERMISSION_GRANTED) {
                                     writeMet = true;
                                 } else {
                                     allowDefaultWrite = false;
@@ -6285,28 +6377,40 @@
             }
         }
 
+        /* There is a special cross user grant if:
+         * - The target is on another user.
+         * - Apps on the current user can access the uri without any uid permissions.
+         * In this case, we grant a uri permission, even if the ContentProvider does not normally
+         * grant uri permissions.
+         */
+        boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+                && checkHoldingPermissionsInternalLocked(pm, pi, grantUri, callingUid,
+                modeFlags, false /*without considering the uid permissions*/);
+
         // Second...  is the provider allowing granting of URI permissions?
-        if (!pi.grantUriPermissions) {
-            throw new SecurityException("Provider " + pi.packageName
-                    + "/" + pi.name
-                    + " does not allow granting of Uri permissions (uri "
-                    + grantUri + ")");
-        }
-        if (pi.uriPermissionPatterns != null) {
-            final int N = pi.uriPermissionPatterns.length;
-            boolean allowed = false;
-            for (int i=0; i<N; i++) {
-                if (pi.uriPermissionPatterns[i] != null
-                        && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
-                    allowed = true;
-                    break;
-                }
-            }
-            if (!allowed) {
+        if (!specialCrossUserGrant) {
+            if (!pi.grantUriPermissions) {
                 throw new SecurityException("Provider " + pi.packageName
                         + "/" + pi.name
-                        + " does not allow granting of permission to path of Uri "
-                        + grantUri);
+                        + " does not allow granting of Uri permissions (uri "
+                        + grantUri + ")");
+            }
+            if (pi.uriPermissionPatterns != null) {
+                final int N = pi.uriPermissionPatterns.length;
+                boolean allowed = false;
+                for (int i=0; i<N; i++) {
+                    if (pi.uriPermissionPatterns[i] != null
+                            && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
+                        allowed = true;
+                        break;
+                    }
+                }
+                if (!allowed) {
+                    throw new SecurityException("Provider " + pi.packageName
+                            + "/" + pi.name
+                            + " does not allow granting of permission to path of Uri "
+                            + grantUri);
+                }
             }
         }
 
@@ -6394,7 +6498,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 +6517,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 +6550,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 +6563,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 +6590,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;
         }
@@ -7108,7 +7228,7 @@
         // Compose the recent task info
         ActivityManager.RecentTaskInfo rti
                 = new ActivityManager.RecentTaskInfo();
-        rti.id = tr.mActivities.isEmpty() ? -1 : tr.taskId;
+        rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
         rti.persistentId = tr.taskId;
         rti.baseIntent = new Intent(tr.getBaseIntent());
         rti.origActivity = tr.origActivity;
@@ -7116,6 +7236,7 @@
         rti.stackId = tr.stack != null ? tr.stack.mStackId : -1;
         rti.userId = tr.userId;
         rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+        rti.lastActiveTime = tr.lastActiveTime;
         return rti;
     }
 
@@ -7175,6 +7296,12 @@
                             continue;
                         }
                     }
+                    if (tr.intent != null &&
+                            (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS)
+                            != 0 && tr.getTopActivity() == null) {
+                        // Don't include auto remove tasks that are finished or finishing.
+                        continue;
+                    }
 
                     ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
                     if (!detailed) {
@@ -7394,6 +7521,10 @@
                     Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
                     return;
                 }
+                final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
+                if (prev != null && prev.isRecentsActivity()) {
+                    task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE);
+                }
                 mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
             } finally {
                 Binder.restoreCallingIdentity(origId);
@@ -7603,14 +7734,32 @@
         }
     }
 
-    private boolean isLockTaskAuthorized(ComponentName name) {
+    private boolean isLockTaskAuthorized(String pkg) {
         final DevicePolicyManager dpm = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        return dpm != null && dpm.isLockTaskPermitted(name);
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(pkg,
+                    Binder.getCallingUserHandle().getIdentifier());
+            return (uid == Binder.getCallingUid()) && dpm != null && dpm.isLockTaskPermitted(pkg);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
     }
 
-    private void startLockTaskMode(TaskRecord task) {
-        if (!isLockTaskAuthorized(task.intent.getComponent())) {
+    void startLockTaskMode(TaskRecord task) {
+        final String pkg;
+        synchronized (this) {
+            pkg = task.intent.getComponent().getPackageName();
+        }
+        boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;
+        if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {
+            final TaskRecord taskRecord = task;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mLockToAppRequest.showLockTaskPrompt(taskRecord);
+                }
+            });
             return;
         }
         long ident = Binder.clearCallingIdentity();
@@ -7619,7 +7768,11 @@
                 // Since we lost lock on task, make sure it is still there.
                 task = mStackSupervisor.anyTaskForIdLocked(task.taskId);
                 if (task != null) {
-                    mStackSupervisor.setLockTaskModeLocked(task);
+                    if (!isSystemInitiated
+                            && ((mFocusedActivity == null) || (task != mFocusedActivity.task))) {
+                        throw new IllegalArgumentException("Invalid task, not in foreground");
+                    }
+                    mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated);
                 }
             }
         } finally {
@@ -7629,25 +7782,25 @@
 
     @Override
     public void startLockTaskMode(int taskId) {
+        final TaskRecord task;
         long ident = Binder.clearCallingIdentity();
         try {
-            final TaskRecord task;
             synchronized (this) {
                 task = mStackSupervisor.anyTaskForIdLocked(taskId);
             }
-            if (task != null) {
-                startLockTaskMode(task);
-            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+        if (task != null) {
+            startLockTaskMode(task);
+        }
     }
 
     @Override
     public void startLockTaskMode(IBinder token) {
+        final TaskRecord task;
         long ident = Binder.clearCallingIdentity();
         try {
-            final TaskRecord task;
             synchronized (this) {
                 final ActivityRecord r = ActivityRecord.forToken(token);
                 if (r == null) {
@@ -7655,8 +7808,50 @@
                 }
                 task = r.task;
             }
-            if (task != null) {
-                startLockTaskMode(task);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        if (task != null) {
+            startLockTaskMode(task);
+        }
+    }
+
+    @Override
+    public void startLockTaskModeOnCurrent() throws RemoteException {
+        checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+        ActivityRecord r = null;
+        synchronized (this) {
+            r = mStackSupervisor.topRunningActivityLocked();
+        }
+        startLockTaskMode(r.task);
+    }
+
+    @Override
+    public void stopLockTaskMode() {
+        // Verify that the user matches the package of the intent for the TaskRecord
+        // we are locked to or systtem.  This will ensure the same caller for startLockTaskMode
+        // and stopLockTaskMode.
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            try {
+                String pkg =
+                        mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName();
+                int uid = mContext.getPackageManager().getPackageUid(pkg,
+                        Binder.getCallingUserHandle().getIdentifier());
+                if (uid != callingUid) {
+                    throw new SecurityException("Invalid uid, expected " + uid);
+                }
+            } catch (NameNotFoundException e) {
+                Log.d(TAG, "stopLockTaskMode " + e);
+                return;
+            }
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Log.d(TAG, "stopLockTaskMode");
+            // Stop lock task
+            synchronized (this) {
+                mStackSupervisor.setLockTaskModeLocked(null, false);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -7664,21 +7859,13 @@
     }
 
     @Override
-    public void stopLockTaskMode() {
-        // Check if the calling task is eligible to use lock task
-        final int uid = Binder.getCallingUid();
+    public void stopLockTaskModeOnCurrent() throws RemoteException {
+        checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
+        long ident = Binder.clearCallingIdentity();
         try {
-            final String name = AppGlobals.getPackageManager().getNameForUid(uid);
-            if (!isLockTaskAuthorized(new ComponentName(name, name))) {
-                return;
-            }
-        } catch (RemoteException e) {
-            Log.d(TAG, "stopLockTaskMode " + e);
-            return;
-        }
-        // Stop lock task
-        synchronized (this) {
-            mStackSupervisor.setLockTaskModeLocked(null);
+            stopLockTaskMode();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -7737,7 +7924,8 @@
                     // to run in multiple processes, because this is actually
                     // part of the framework so doesn't make sense to track as a
                     // separate apk in the process.
-                    app.addPackage(cpi.applicationInfo.packageName, mProcessStats);
+                    app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
+                            mProcessStats);
                 }
                 ensurePackageDexOpt(cpi.applicationInfo.packageName);
             }
@@ -7751,7 +7939,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 +7956,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 +8094,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 +8201,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 +8751,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 +8787,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 +10005,7 @@
                                 = (ApplicationInfo)apps.get(i);
                             if (info != null &&
                                     !info.packageName.equals("android")) {
-                                addAppLocked(info, false);
+                                addAppLocked(info, false, null /* ABI override */);
                             }
                         }
                     }
@@ -10577,8 +10796,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 +10885,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) {
@@ -12516,6 +12733,8 @@
             }
         }
 
+        long nativeProcTotalPss = 0;
+
         if (!isCheckinRequest && procs.size() > 1) {
             // If we are showing aggregations, also look for native processes to
             // include so that our aggregations are more accurate.
@@ -12537,6 +12756,7 @@
 
                         final long myTotalPss = mi.getTotalPss();
                         totalPss += myTotalPss;
+                        nativeProcTotalPss += myTotalPss;
 
                         MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                                 st.name, myTotalPss, st.pid, false);
@@ -12604,6 +12824,14 @@
             }
             MemInfoReader memInfo = new MemInfoReader();
             memInfo.readMemInfo();
+            if (nativeProcTotalPss > 0) {
+                synchronized (this) {
+                    mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                            memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
+                            memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(),
+                            nativeProcTotalPss);
+                }
+            }
             if (!brief) {
                 if (!isCompact) {
                     pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
@@ -12968,7 +13196,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 +13336,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 +13356,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 +13386,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 +13399,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 +13415,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) {
@@ -13249,7 +13494,7 @@
     // activity manager to announce its creation.
     public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
         if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
-        enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent");
+        enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent");
 
         synchronized(this) {
             // !!! TODO: currently no check here that we're already bound
@@ -13716,6 +13961,8 @@
 
         // Make sure that the user who is receiving this broadcast is started.
         // If not, we will just skip it.
+
+
         if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
             if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
                     & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
@@ -13731,8 +13978,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
+            || callingAppId == Process.NFC_UID || callingUid == 0) {
             // Always okay.
         } else if (callerApp == null || !callerApp.persistent) {
             try {
@@ -14265,7 +14512,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 +14561,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;
@@ -15367,6 +15614,7 @@
         }
         if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs!  memLowered=" + memLowered);
         mLastFullPssTime = now;
+        mFullPssPending = true;
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
         for (int i=mLruProcesses.size()-1; i>=0; i--) {
@@ -16278,7 +16526,7 @@
                     
                     if (app.persistent) {
                         if (app.persistent) {
-                            addAppLocked(app.info, false);
+                            addAppLocked(app.info, false, null /* ABI override */);
                         }
                     }
                 }
@@ -16542,12 +16790,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);
         }
@@ -16562,7 +16810,7 @@
                     return true;
                 }
 
-                mStackSupervisor.setLockTaskModeLocked(null);
+                mStackSupervisor.setLockTaskModeLocked(null, false);
 
                 final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
                 if (userInfo == null) {
@@ -16913,12 +17161,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 +17304,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 +17390,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..287ad00 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.TaskDescription;
 import android.os.PersistableBundle;
 import android.os.Trace;
+
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.util.XmlUtils;
 import com.android.server.AttributeCache;
@@ -49,6 +50,7 @@
 import android.util.TimeUtils;
 import android.view.IApplicationToken;
 import android.view.WindowManager;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -58,6 +60,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Objects;
 
 /**
  * An entry in the history stack, representing an activity.
@@ -76,13 +79,12 @@
     private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package";
     private static final String ATTR_RESOLVEDTYPE = "resolved_type";
     private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
-    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
-    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
     private static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
 
     final ActivityManagerService service; // owner
     final IApplicationToken.Stub appToken; // window manager token
     final ActivityInfo info; // all about me
+    final ApplicationInfo appInfo; // information about activity's app
     final int launchedFromUid; // always the uid who started the activity.
     final String launchedFromPackage; // always the package who started the activity.
     final int userId;          // Which user is this running for?
@@ -103,9 +105,6 @@
     static final int RECENTS_ACTIVITY_TYPE = 2;
     int mActivityType;
 
-    final String baseDir;   // where activity source (resources etc) located
-    final String resDir;   // where public activity source (public resources etc) located
-    final String dataDir;   // where activity data should go
     CharSequence nonLocalizedLabel;  // the label information from the package mgr.
     int labelRes;           // the label information from the package mgr.
     int icon;               // resource identifier of activity's icon.
@@ -184,11 +183,13 @@
         pw.print(prefix); pw.print("taskAffinity="); pw.println(taskAffinity);
         pw.print(prefix); pw.print("realActivity=");
                 pw.println(realActivity.flattenToShortString());
-        pw.print(prefix); pw.print("baseDir="); pw.println(baseDir);
-        if (!resDir.equals(baseDir)) {
-            pw.print(prefix); pw.print("resDir="); pw.println(resDir);
+        if (appInfo != null) {
+            pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir);
+            if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+                pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir);
+            }
+            pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir);
         }
-        pw.print(prefix); pw.print("dataDir="); pw.println(dataDir);
         pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded);
                 pw.print(" componentSpecified="); pw.print(componentSpecified);
                 pw.print(" mActivityType="); pw.println(mActivityType);
@@ -418,9 +419,7 @@
             taskAffinity = aInfo.taskAffinity;
             stateNotNeeded = (aInfo.flags&
                     ActivityInfo.FLAG_STATE_NOT_NEEDED) != 0;
-            baseDir = aInfo.applicationInfo.sourceDir;
-            resDir = aInfo.applicationInfo.publicSourceDir;
-            dataDir = aInfo.applicationInfo.dataDir;
+            appInfo = aInfo.applicationInfo;
             nonLocalizedLabel = aInfo.nonLocalizedLabel;
             labelRes = aInfo.labelRes;
             if (nonLocalizedLabel == null && labelRes == 0) {
@@ -488,9 +487,7 @@
             realActivity = null;
             taskAffinity = null;
             stateNotNeeded = false;
-            baseDir = null;
-            resDir = null;
-            dataDir = null;
+            appInfo = null;
             processName = null;
             packageName = null;
             fullscreen = true;
@@ -568,7 +565,10 @@
     }
 
     boolean isPersistable() {
-        return (info.flags & ActivityInfo.FLAG_PERSISTABLE) != 0;
+        return (info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY ||
+                info.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS) &&
+                (intent == null ||
+                        (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
     }
 
     void makeFinishing() {
@@ -580,11 +580,6 @@
         }
     }
 
-    boolean isRootActivity() {
-        final ArrayList<ActivityRecord> activities = task.mActivities;
-        return activities.size() == 0 || this == activities.get(0);
-    }
-
     UriPermissionOwner getUriPermissionsLocked() {
         if (uriPermissions == null) {
             uriPermissions = new UriPermissionOwner(service, this);
@@ -635,7 +630,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
@@ -1033,11 +1028,11 @@
             return -1;
         }
         final TaskRecord task = r.task;
-        switch (task.mActivities.indexOf(r)) {
-            case -1: return -1;
-            case 0: return task.taskId;
-            default: return onlyRoot ? -1 : task.taskId;
+        final int activityNdx = task.mActivities.indexOf(r);
+        if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
+            return -1;
         }
+        return task.taskId;
     }
 
     static ActivityRecord isInStackLocked(IBinder token) {
@@ -1067,20 +1062,10 @@
         }
         out.attribute(null, ATTR_COMPONENTSPECIFIED, String.valueOf(componentSpecified));
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
+
         if (taskDescription != null) {
-            final String label = taskDescription.getLabel();
-            if (label != null) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
-            }
-            final int colorPrimary = taskDescription.getPrimaryColor();
-            if (colorPrimary != 0) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
-            }
-            final Bitmap icon = taskDescription.getIcon();
-            if (icon != null) {
-                TaskPersister.saveImage(icon, String.valueOf(task.taskId) + ACTIVITY_ICON_SUFFIX +
-                        createTime);
-            }
+            TaskPersister.saveTaskDescription(taskDescription, String.valueOf(task.taskId) +
+                    ACTIVITY_ICON_SUFFIX + createTime, out);
         }
 
         out.startTag(null, TAG_INTENT);
@@ -1103,10 +1088,9 @@
         String resolvedType = null;
         boolean componentSpecified = false;
         int userId = 0;
-        String activityLabel = null;
-        int activityColor = 0;
         long createTime = -1;
         final int outerDepth = in.getDepth();
+        TaskDescription taskDescription = new TaskDescription();
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
@@ -1125,10 +1109,9 @@
                 componentSpecified = Boolean.valueOf(attrValue);
             } else if (ATTR_USERID.equals(attrName)) {
                 userId = Integer.valueOf(attrValue);
-            } else if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
-                activityLabel = attrValue;
-            } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
-                activityColor = (int) Long.parseLong(attrValue, 16);
+            } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+                    attrValue)) {
+                // Completed in TaskPersister.readTaskDescriptionAttribute()
             } else {
                 Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName);
             }
@@ -1164,7 +1147,8 @@
         final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null,
                 null, userId);
         if (aInfo == null) {
-            throw new XmlPullParserException("restoreActivity resolver error.");
+            throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
+                    " resolvedType=" + resolvedType);
         }
         final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid,
                 launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(),
@@ -1172,12 +1156,11 @@
 
         r.persistentState = persistentState;
 
-        Bitmap icon = null;
         if (createTime >= 0) {
-            icon = TaskPersister.restoreImage(String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX +
-                    createTime);
+            taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+                    ACTIVITY_ICON_SUFFIX + createTime));
         }
-        r.taskDescription = new TaskDescription(activityLabel, icon, activityColor);
+        r.taskDescription = taskDescription;
         r.createTime = createTime;
 
         return r;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d5a50e7..e1c7da9 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -30,14 +30,18 @@
 import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY;
 import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS;
 
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
+
 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 +78,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;
@@ -1065,40 +1070,6 @@
         }
     }
 
-    /**
-     * Determine if home should be visible below the passed record.
-     * @param record activity we are querying for.
-     * @return true if home is visible below the passed activity, false otherwise.
-     */
-    boolean isActivityOverHome(ActivityRecord record) {
-        // Start at record and go down, look for either home or a visible fullscreen activity.
-        final TaskRecord recordTask = record.task;
-        for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
-            TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            final int startNdx =
-                    task == recordTask ? activities.indexOf(record) : activities.size() - 1;
-            for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                if (r.isHomeActivity()) {
-                    return true;
-                }
-                if (!r.finishing && r.fullscreen) {
-                    // Passed activity is over a fullscreen activity.
-                    return false;
-                }
-            }
-            if (task.mOnTopOfHome) {
-                // Got to the bottom of a task on top of home without finding a visible fullscreen
-                // activity. Home is visible.
-                return true;
-            }
-        }
-        // Got to the bottom of this stack and still don't know. If this is over the home stack
-        // then record is over home. May not work if we ever get more than two layers.
-        return mStackSupervisor.isFrontStack(this);
-    }
-
     private void setVisibile(ActivityRecord r, boolean visible) {
         r.visible = visible;
         mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1128,7 +1099,8 @@
         for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
             final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks();
             for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) {
-                final ArrayList<ActivityRecord> activities = tasks.get(taskNdx).mActivities;
+                final TaskRecord task = tasks.get(taskNdx);
+                final ArrayList<ActivityRecord> activities = task.mActivities;
                 for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) {
                     final ActivityRecord r = activities.get(activityNdx);
 
@@ -1139,7 +1111,7 @@
                     // - Full Screen Activity OR
                     // - On top of Home and our stack is NOT home
                     if (!r.finishing && r.visible && (r.fullscreen ||
-                            (!isHomeStack() && r.frontOfTask && tasks.get(taskNdx).mOnTopOfHome))) {
+                            (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()))) {
                         return false;
                     }
                 }
@@ -1267,7 +1239,7 @@
                         // At this point, nothing else needs to be shown
                         if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
                         behindFullscreen = true;
-                    } else if (!isHomeStack() && r.frontOfTask && task.mOnTopOfHome) {
+                    } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
                         if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
                         behindFullscreen = true;
                     }
@@ -1405,7 +1377,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;
@@ -1421,6 +1393,7 @@
         final boolean userLeaving = mStackSupervisor.mUserLeaving;
         mStackSupervisor.mUserLeaving = false;
 
+        final TaskRecord prevTask = prev != null ? prev.task : null;
         if (next == null) {
             // There are no more activities!  Let's just start up the
             // Launcher...
@@ -1428,7 +1401,10 @@
             if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
             // Only resume home if on home display
-            return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
+            final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
+                    HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
+            return isOnHomeDisplay() &&
+                    mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
         }
 
         next.delayedResume = false;
@@ -1447,22 +1423,24 @@
         }
 
         final TaskRecord nextTask = next.task;
-        final TaskRecord prevTask = prev != null ? prev.task : null;
         if (prevTask != null && prevTask.stack == this &&
-                prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
+                prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
             if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
             if (prevTask == nextTask) {
                 prevTask.setFrontOfTask();
             } else if (prevTask != topTask()) {
-                // This task is going away but it was supposed to return to the home task.
+                // This task is going away but it was supposed to return to the home stack.
                 // Now the task above it has to return to the home task instead.
                 final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
-                mTaskHistory.get(taskNdx).mOnTopOfHome = true;
+                mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE);
             } else {
                 if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG,
                         "resumeTopActivityLocked: Launching home next");
                 // Only resume home if on home display
-                return isOnHomeDisplay() && mStackSupervisor.resumeHomeActivity(prev);
+                final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
+                        HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
+                return isOnHomeDisplay() &&
+                        mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
             }
         }
 
@@ -1833,10 +1811,11 @@
             ActivityStack lastStack = mStackSupervisor.getLastStack();
             final boolean fromHome = lastStack.isHomeStack();
             if (!isHomeStack() && (fromHome || topTask() != task)) {
-                task.mOnTopOfHome = fromHome;
+                task.setTaskToReturnTo(fromHome ?
+                        lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
             }
         } else {
-            task.mOnTopOfHome = false;
+            task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
         }
 
         mTaskHistory.remove(task);
@@ -1953,8 +1932,7 @@
                 // existing activities from other tasks in to it.
                 // If the caller has requested that the target task be
                 // reset, then do so.
-                if ((r.intent.getFlags()
-                        & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                     resetTaskIfNeededLocked(r, r);
                     doShow = topRunningNonDelayedActivityLocked(null) == r;
                 }
@@ -2047,7 +2025,8 @@
         // the root, we may no longer have the task!).
         final ArrayList<ActivityRecord> activities = task.mActivities;
         final int numActivities = activities.size();
-        for (int i = numActivities - 1; i > 0; --i ) {
+        final int rootActivityNdx = task.findEffectiveRootIndex();
+        for (int i = numActivities - 1; i > rootActivityNdx; --i ) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
@@ -2206,8 +2185,10 @@
 
         final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
         final int numActivities = activities.size();
-        // Do not operate on the root Activity.
-        for (int i = numActivities - 1; i > 0; --i) {
+        final int rootActivityNdx = affinityTask.findEffectiveRootIndex();
+
+        // Do not operate on or below the effective root Activity.
+        for (int i = numActivities - 1; i > rootActivityNdx; --i) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
@@ -2355,7 +2336,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
@@ -2381,8 +2362,8 @@
             ActivityRecord next = topRunningActivityLocked(null);
             if (next != r) {
                 final TaskRecord task = r.task;
-                if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) {
-                    mStackSupervisor.moveHomeToTop();
+                if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
+                    mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
                 }
             }
             ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
@@ -2538,10 +2519,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 +2658,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;
         }
 
@@ -2866,8 +2855,9 @@
         if (task != null && task.removeActivity(r)) {
             if (DEBUG_STACK) Slog.i(TAG,
                     "removeActivityFromHistoryLocked: last activity removed from " + this);
-            if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) {
-                mStackSupervisor.moveHomeToTop();
+            if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
+                    task.isOverHomeStack()) {
+                mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo());
             }
             removeTask(task);
         }
@@ -3039,6 +3029,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) {
@@ -3182,12 +3173,13 @@
         }
     }
 
-    void moveHomeTaskToTop() {
+    void moveHomeStackTaskToTop(int homeStackTaskType) {
         final int top = mTaskHistory.size() - 1;
         for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            if (task.isHomeTask()) {
-                if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
+            if (task.taskType == homeStackTaskType) {
+                if (DEBUG_TASKS || DEBUG_STACK)
+                    Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task);
                 mTaskHistory.remove(taskNdx);
                 mTaskHistory.add(top, task);
                 updateTaskMovement(task, true);
@@ -3299,12 +3291,12 @@
         int numTasks = mTaskHistory.size();
         for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            if (task.mOnTopOfHome) {
+            if (task.isOverHomeStack()) {
                 break;
             }
             if (taskNdx == 1) {
                 // Set the last task before tr to go to home.
-                task.mOnTopOfHome = true;
+                task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
             }
         }
 
@@ -3325,9 +3317,10 @@
         }
 
         final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
-        if (task == tr && tr.mOnTopOfHome || numTasks <= 1 && isOnHomeDisplay()) {
-            tr.mOnTopOfHome = false;
-            return mStackSupervisor.resumeHomeActivity(null);
+        if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) {
+            final int taskToReturnTo = tr.getTaskToReturnTo();
+            tr.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+            return mStackSupervisor.resumeHomeStackTask(taskToReturnTo, null);
         }
 
         mStackSupervisor.resumeTopActivitiesLocked();
@@ -3768,8 +3761,11 @@
 
         final int taskNdx = mTaskHistory.indexOf(task);
         final int topTaskNdx = mTaskHistory.size() - 1;
-        if (task.mOnTopOfHome && taskNdx < topTaskNdx) {
-            mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
+        if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
+            final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
+            if (!nextTask.isOverHomeStack()) {
+                nextTask.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+            }
         }
         mTaskHistory.remove(task);
         updateTaskMovement(task, true);
@@ -3798,6 +3794,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..e7f5720 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -31,6 +31,9 @@
 import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
 import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
 import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -46,6 +49,8 @@
 import android.app.IActivityManager.WaitResult;
 import android.app.ResultInfo;
 import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentSender;
@@ -77,6 +82,8 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.EventLog;
 import android.util.Slog;
@@ -107,10 +114,11 @@
     static final boolean DEBUG = ActivityManagerService.DEBUG || false;
     static final boolean DEBUG_ADD_REMOVE = DEBUG || false;
     static final boolean DEBUG_APP = DEBUG || false;
-    static final boolean DEBUG_SAVED_STATE = DEBUG || false;
-    static final boolean DEBUG_STATES = DEBUG || false;
+    static final boolean DEBUG_CONTAINERS = DEBUG || false;
     static final boolean DEBUG_IDLE = DEBUG || false;
+    static final boolean DEBUG_SAVED_STATE = DEBUG || false;
     static final boolean DEBUG_SCREENSHOTS = DEBUG || false;
+    static final boolean DEBUG_STATES = DEBUG || false;
 
     public static final int HOME_STACK_ID = 0;
 
@@ -134,12 +142,17 @@
     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";
 
+    private static final String LOCK_TASK_TAG = "Lock-to-App";
+
     /** Status Bar Service **/
     private IBinder mToken = new Binder();
     private IStatusBarService mStatusBarService;
+    private IDevicePolicyManager mDevicePolicyManager;
 
     // For debugging to make sure the caller when acquiring/releasing our
     // wake lock is the system process.
@@ -224,21 +237,21 @@
      * receivers to launch an activity and get that to run before the device
      * goes back to sleep.
      */
-    final PowerManager.WakeLock mLaunchingActivity;
+    PowerManager.WakeLock mLaunchingActivity;
 
     /**
      * Set when the system is going to sleep, until we have
      * successfully paused the current activity and released our wake lock.
      * At that point the system is allowed to actually sleep.
      */
-    final PowerManager.WakeLock mGoingToSleep;
+    PowerManager.WakeLock mGoingToSleep;
 
     /** Stack id of the front stack when user switched, indexed by userId. */
     SparseIntArray mUserStackInFront = new SparseIntArray(2);
 
     // 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 =
@@ -248,16 +261,24 @@
 
     /** If non-null then the task specified remains in front and no other tasks may be started
      * until the task exits or #stopLockTaskMode() is called. */
-    private TaskRecord mLockTaskModeTask;
+    TaskRecord mLockTaskModeTask;
+    /**
+     * Notifies the user when entering/exiting lock-task.
+     */
+    private LockTaskNotify mLockTaskNotify;
 
     public ActivityStackSupervisor(ActivityManagerService service) {
         mService = service;
+        mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
+    }
+
+    /**
+     * At the time when the constructor runs, the power manager has not yet been
+     * initialized.  So we initialize our wakelocks afterwards.
+     */
+    void initPowerManagement() {
         PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE);
         mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep");
-        mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
-        if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
-            throw new IllegalStateException("Calling must be system uid");
-        }
         mLaunchingActivity =
                 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch");
         mLaunchingActivity.setReferenceCounted(false);
@@ -278,6 +299,19 @@
         }
     }
 
+    private IDevicePolicyManager getDevicePolicyManager() {
+        synchronized (mService) {
+            if (mDevicePolicyManager == null) {
+                mDevicePolicyManager = IDevicePolicyManager.Stub.asInterface(
+                    ServiceManager.checkService(Context.DEVICE_POLICY_SERVICE));
+                if (mDevicePolicyManager == null) {
+                    Slog.w(TAG, "warning: no DEVICE_POLICY_SERVICE");
+                }
+            }
+            return mDevicePolicyManager;
+        }
+    }
+
     void setWindowManager(WindowManagerService wm) {
         synchronized (mService) {
             mWindowManager = wm;
@@ -351,17 +385,27 @@
         }
     }
 
-    void moveHomeToTop() {
+    void moveHomeStackTaskToTop(int homeStackTaskType) {
+        if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
+            mWindowManager.showRecentApps();
+            return;
+        }
         moveHomeStack(true);
-        mHomeStack.moveHomeTaskToTop();
+        mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
     }
 
-    boolean resumeHomeActivity(ActivityRecord prev) {
-        moveHomeToTop();
-        if (prev != null) {
-            prev.task.mOnTopOfHome = false;
+    boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev) {
+        if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
+            mWindowManager.showRecentApps();
+            return false;
         }
+        moveHomeStackTaskToTop(homeStackTaskType);
+        if (prev != null) {
+            prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+        }
+
         ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
+        // if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) {
         if (r != null && r.isHomeActivity()) {
             mService.setFocusedActivityLocked(r);
             return resumeTopActivitiesLocked(mHomeStack, prev, null);
@@ -716,7 +760,7 @@
     }
 
     void startHomeActivity(Intent intent, ActivityInfo aInfo) {
-        moveHomeToTop();
+        moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE);
         startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
                 null, false, null, null);
     }
@@ -1155,7 +1199,8 @@
                     // to run in multiple processes, because this is actually
                     // part of the framework so doesn't make sense to track as a
                     // separate apk in the process.
-                    app.addPackage(r.info.packageName, mService.mProcessStats);
+                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
+                            mService.mProcessStats);
                 }
                 realStartActivityLocked(r, app, andResume, checkConfig);
                 return;
@@ -1665,7 +1710,7 @@
                                     (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
                                     == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
                                 // Caller wants to appear on home activity.
-                                intentActivity.task.mOnTopOfHome = true;
+                                intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
                             }
                             options = null;
                         }
@@ -1861,7 +1906,7 @@
                         == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
                     // Caller wants to appear on home activity, so before starting
                     // their own activity we will bring home to the front.
-                    r.task.mOnTopOfHome = r.task.stack.isOnHomeDisplay();
+                    r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
                 }
             }
         } else if (sourceRecord != null) {
@@ -1934,8 +1979,11 @@
         }
 
         mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                intent, r.getUriPermissionsLocked());
+                intent, r.getUriPermissionsLocked(), r.userId);
 
+        if (sourceRecord != null && sourceRecord.isRecentsActivity()) {
+            r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
+        }
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
         }
@@ -2212,7 +2260,7 @@
         if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
             // Caller wants the home activity moved with it.  To accomplish this,
             // we'll just indicate that this task returns to the home task.
-            task.mOnTopOfHome = true;
+            task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
         }
         task.stack.moveTaskToFrontLocked(task, null, options);
         if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
@@ -2254,8 +2302,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 +2314,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 +2323,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);
@@ -2323,7 +2372,7 @@
             }
             mWindowManager.addTask(taskId, stackId, false);
         }
-        resumeHomeActivity(null);
+        resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
     }
 
     void moveTaskToStack(int taskId, int stackId, boolean toTop) {
@@ -2585,7 +2634,7 @@
             }
         } else {
             // Stack was moved to another display while user was swapped out.
-            resumeHomeActivity(null);
+            resumeHomeStackTask(HOME_ACTIVITY_TYPE, null);
         }
         return homeInFront;
     }
@@ -2884,16 +2933,19 @@
 
     @Override
     public void onDisplayAdded(int displayId) {
+        Slog.v(TAG, "Display added displayId=" + displayId);
         mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0));
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
+        Slog.v(TAG, "Display removed displayId=" + displayId);
         mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0));
     }
 
     @Override
     public void onDisplayChanged(int displayId) {
+        Slog.v(TAG, "Display changed displayId=" + displayId);
         mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
     }
 
@@ -2977,13 +3029,16 @@
         return list;
     }
 
-    void setLockTaskModeLocked(TaskRecord task) {
-        final Message lockTaskMsg = Message.obtain();
+    void setLockTaskModeLocked(TaskRecord task, boolean showHomeRecents) {
         if (task == null) {
-            // Take out of lock task mode.
-            mLockTaskModeTask = null;
-            lockTaskMsg.what = LOCK_TASK_END_MSG;
-            mHandler.sendMessage(lockTaskMsg);
+            // Take out of lock task mode if necessary
+            if (mLockTaskModeTask != null) {
+                final Message lockTaskMsg = Message.obtain();
+                lockTaskMsg.arg1 = mLockTaskModeTask.userId;
+                lockTaskMsg.what = LOCK_TASK_END_MSG;
+                mLockTaskModeTask = null;
+                mHandler.sendMessage(lockTaskMsg);
+            }
             return;
         }
         if (isLockTaskModeViolation(task)) {
@@ -2993,7 +3048,12 @@
         mLockTaskModeTask = task;
         findTaskToMoveToFrontLocked(task, 0, null);
         resumeTopActivitiesLocked();
+
+        final Message lockTaskMsg = Message.obtain();
+        lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName();
+        lockTaskMsg.arg1 = mLockTaskModeTask.userId;
         lockTaskMsg.what = LOCK_TASK_START_MSG;
+        lockTaskMsg.arg2 = showHomeRecents ? 1 : 0;
         mHandler.sendMessage(lockTaskMsg);
     }
 
@@ -3085,45 +3145,94 @@
                 } 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 {
+                        if (mLockTaskNotify == null) {
+                            mLockTaskNotify = new LockTaskNotify(mService.mContext);
+                        }
+                        mLockTaskNotify.show(true);
                         if (getStatusBarService() != null) {
-                            getStatusBarService().disable
-                                (StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK,
-                                mToken, mService.mContext.getPackageName());
+                            int flags =
+                                    StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK;
+                            if (msg.arg2 != 0) {
+                                flags ^= StatusBarManager.DISABLE_HOME
+                                        | StatusBarManager.DISABLE_RECENT;
+                            }
+                            getStatusBarService().disable(flags, mToken,
+                                    mService.mContext.getPackageName());
+                        }
+                        mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+                        if (getDevicePolicyManager() != null) {
+                            getDevicePolicyManager().notifyLockTaskModeChanged(true,
+                                    (String)msg.obj, msg.arg1);
                         }
                     } catch (RemoteException ex) {
                         throw new RuntimeException(ex);
                     }
-                    break;
-                }
+                } break;
                 case LOCK_TASK_END_MSG: {
                     // When lock task ends, we enable the status bars.
                     try {
-                       if (getStatusBarService() != null) {
-                           getStatusBarService().disable
-                               (StatusBarManager.DISABLE_NONE,
-                               mToken, mService.mContext.getPackageName());
-                       }
+                        if (getStatusBarService() != null) {
+                            getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken,
+                                    mService.mContext.getPackageName());
+                        }
+                        mWindowManager.reenableKeyguard(mToken);
+                        if (getDevicePolicyManager() != null) {
+                            getDevicePolicyManager().notifyLockTaskModeChanged(false, null,
+                                    msg.arg1);
+                        }
+                        if (mLockTaskNotify == null) {
+                            mLockTaskNotify = new LockTaskNotify(mService.mContext);
+                        }
+                        mLockTaskNotify.show(false);
+                        try {
+                            boolean shouldLockKeyguard = Settings.System.getInt(
+                                    mService.mContext.getContentResolver(),
+                                    Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0;
+                            if (shouldLockKeyguard) {
+                                mWindowManager.lockNow(null);
+                            }
+                        } catch (SettingNotFoundException e) {
+                            // No setting, don't lock.
+                        }
                     } 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 +3282,10 @@
 
         @Override
         public int getDisplayId() {
-            if (mActivityDisplay != null) {
-                return mActivityDisplay.mDisplayId;
+            synchronized (mService) {
+                if (mActivityDisplay != null) {
+                    return mActivityDisplay.mDisplayId;
+                }
             }
             return -1;
         }
@@ -3183,10 +3294,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 +3309,23 @@
 
         @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);
+
+                long origId = Binder.clearCallingIdentity();
+                try {
+                    mStack.finishAllActivitiesLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
         }
 
         private void detachLocked() {
@@ -3290,15 +3416,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 +3449,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 +3473,7 @@
             mParentActivity = parent;
             mCallback = callback;
             mContainerState = CONTAINER_STATE_NO_SURFACE;
-            mIdString = "VirtualActivtyContainer{" + mStackId + ", parent=" + mParentActivity + "}";
+            mIdString = "VirtualActivityContainer{" + mStackId + ", parent=" + mParentActivity + "}";
         }
 
         @Override
@@ -3382,22 +3519,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 +3544,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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index eb253eb..3cbc6e2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -74,12 +74,19 @@
         mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout)
                 * 1000L);
+    }
+
+    /**
+     * At the time when the constructor runs, the power manager has not yet been
+     * initialized.  So we initialize the low power observer later.
+     */
+    public void initPowerManagement() {
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         mPowerManagerInternal.registerLowPowerModeObserver(this);
         mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled());
         (new WakeupReasonThread()).start();
-     }
-    
+    }
+
     public void shutdown() {
         Slog.w("BatteryStats", "Writing battery stats before shutdown...");
         synchronized (mStats) {
@@ -386,6 +393,20 @@
         }
     }
 
+    public void noteWifiSupplicantStateChanged(int supplState, boolean failedAuth) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiSupplicantStateChangedLocked(supplState, failedAuth);
+        }
+    }
+
+    public void noteWifiRssiChanged(int newRssi) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiRssiChangedLocked(newRssi);
+        }
+    }
+
     public void noteBluetoothOn() {
         enforceCallingPermission();
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 9d6481a..cdcc74b 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;
@@ -883,7 +887,8 @@
                     info.activityInfo.applicationInfo.uid, false);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(info.activityInfo.packageName, mService.mProcessStats);
+                    app.addPackage(info.activityInfo.packageName,
+                            info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                     processCurBroadcastLocked(r, app);
                     return;
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
new file mode 100644
index 0000000..1997f46
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -0,0 +1,185 @@
+/*
+ * 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.server.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ *  Helper to manage showing/hiding a image to notify them that they are entering
+ *  or exiting lock-to-app mode.
+ */
+public class LockTaskNotify {
+    private static final String TAG = "LockTaskNotify";
+
+    private static final int SHOW_LENGTH_MS = 1500;
+
+    private final Context mContext;
+    private final H mHandler;
+
+    private ClingWindowView mClingWindow;
+    private WindowManager mWindowManager;
+    private boolean mIsStarting;
+
+    public LockTaskNotify(Context context) {
+        mContext = context;
+        mHandler = new H();
+        mWindowManager = (WindowManager)
+                mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    public void show(boolean starting) {
+        mIsStarting = starting;
+        mHandler.obtainMessage(H.SHOW).sendToTarget();
+    }
+
+    public WindowManager.LayoutParams getClingWindowLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_TOAST,
+                0
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                ,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("LockTaskNotify");
+        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
+        lp.gravity = Gravity.FILL;
+        return lp;
+    }
+
+    public FrameLayout.LayoutParams getImageLayoutParams() {
+        return new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+    }
+
+    private void handleShow() {
+        mClingWindow = new ClingWindowView(mContext);
+
+        // we will be hiding the nav bar, so layout as if it's already hidden
+        mClingWindow.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+              | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+        // show the confirmation
+        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        mWindowManager.addView(mClingWindow, lp);
+    }
+
+    private void handleHide() {
+        if (mClingWindow != null) {
+            mWindowManager.removeView(mClingWindow);
+            mClingWindow = null;
+        }
+    }
+
+
+    private class ClingWindowView extends FrameLayout {
+        private View mView;
+
+        private Runnable mUpdateLayoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mView != null && mView.getParent() != null) {
+                    mView.setLayoutParams(getImageLayoutParams());
+                }
+            }
+        };
+
+        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                    post(mUpdateLayoutRunnable);
+                }
+            }
+        };
+
+        public ClingWindowView(Context context) {
+            super(context);
+            setClickable(true);
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            mWindowManager.getDefaultDisplay().getMetrics(metrics);
+
+            int id = R.layout.lock_to_app_exit;
+            if (mIsStarting) {
+                id = R.layout.lock_to_app_enter;
+            }
+            mView = View.inflate(getContext(), id, null);
+
+            addView(mView, getImageLayoutParams());
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(H.HIDE), SHOW_LENGTH_MS);
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            mContext.unregisterReceiver(mReceiver);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent motion) {
+            Slog.v(TAG, "ClingWindowView.onTouchEvent");
+            return true;
+        }
+    }
+
+    private final class H extends Handler {
+        private static final int SHOW = 1;
+        private static final int HIDE = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case SHOW:
+                    handleShow();
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
new file mode 100644
index 0000000..6e86dff
--- /dev/null
+++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java
@@ -0,0 +1,87 @@
+
+package com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.BitmapFactory;
+import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class LockToAppRequestDialog implements OnClickListener {
+    private static final String TAG = "ActivityManager";
+
+    final private Context mContext;
+    final private ActivityManagerService mService;
+
+    private AlertDialog mDialog;
+    private TaskRecord mRequestedTask;
+
+    public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) {
+        mContext = context;
+        mService = activityManagerService;
+    }
+
+    public void showLockTaskPrompt(TaskRecord task) {
+        if (mDialog != null) {
+            mDialog.dismiss();
+            mDialog = null;
+        }
+        mRequestedTask = task;
+
+        final Resources r = Resources.getSystem();
+        final String descriptionString = r.getString(R.string.lock_to_app_description);
+        final SpannableString description =
+                new SpannableString(descriptionString.replace('$', ' '));
+        final ImageSpan imageSpan = new ImageSpan(mContext,
+                BitmapFactory.decodeResource(r, R.drawable.ic_recent),
+                DynamicDrawableSpan.ALIGN_BOTTOM);
+        final int index = descriptionString.indexOf('$');
+        if (index >= 0) {
+            description.setSpan(imageSpan, index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+        mDialog =
+                new AlertDialog.Builder(mContext)
+                        .setTitle(r.getString(R.string.lock_to_app_title))
+                        .setMessage(description)
+                        .setPositiveButton(r.getString(R.string.lock_to_app_positive), this)
+                        .setNegativeButton(r.getString(R.string.lock_to_app_negative), this)
+                        .create();
+
+        mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        mDialog.show();
+
+        // Make icon fit.
+        final TextView msgTxt = (TextView) mDialog.findViewById(R.id.message);
+        final float width = imageSpan.getDrawable().getIntrinsicWidth();
+        final float height = imageSpan.getDrawable().getIntrinsicHeight();
+        final int lineHeight = msgTxt.getLineHeight();
+        imageSpan.getDrawable().setBounds(0, 0, (int) (lineHeight * width / height), lineHeight);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (DialogInterface.BUTTON_POSITIVE == which) {
+            Slog.d(TAG, "accept lock-to-app request");
+            // Automatically enable if not currently on. (Could be triggered by an app)
+            Settings.System.putInt(mContext.getContentResolver(),
+                    Settings.System.LOCK_TO_APP_ENABLED, 1);
+
+            // Start lock-to-app.
+            mService.startLockTaskMode(mRequestedTask);
+        } else {
+            Slog.d(TAG, "ignore lock-to-app request");
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 8d7d300..2f25bd4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -53,8 +53,8 @@
     final int userId;           // user of process.
     final String processName;   // name of the process
     // List of packages running in the process
-    final ArrayMap<String, ProcessStats.ProcessState> pkgList
-            = new ArrayMap<String, ProcessStats.ProcessState>();
+    final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList
+            = new ArrayMap<String, ProcessStats.ProcessStateHolder>();
     IApplicationThread thread;  // the actual proc...  may be null only if
                                 // 'persistent' is true (in which case we
                                 // are in the process of launching the app)
@@ -371,7 +371,7 @@
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        pkgList.put(_info.packageName, null);
+        pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));
         maxAdj = ProcessList.UNKNOWN_ADJ;
         curRawAdj = setRawAdj = -100;
         curAdj = setAdj = -100;
@@ -398,16 +398,15 @@
                     info.versionCode, processName);
             baseProcessTracker.makeActive();
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+                holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
                         info.versionCode, processName);
-                if (ps != baseProcessTracker) {
-                    ps.makeActive();
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
-                pkgList.setValueAt(i, ps);
             }
         }
         thread = _thread;
@@ -424,11 +423,11 @@
             }
             baseProcessTracker = null;
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                pkgList.setValueAt(i, null);
+                holder.state = null;
             }
         }
     }
@@ -572,17 +571,19 @@
     /*
      *  Return true if package has been added false if not
      */
-    public boolean addPackage(String pkg, ProcessStatsService tracker) {
+    public boolean addPackage(String pkg, int versionCode, ProcessStatsService tracker) {
         if (!pkgList.containsKey(pkg)) {
+            ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                    versionCode);
             if (baseProcessTracker != null) {
-                ProcessStats.ProcessState state = tracker.getProcessStateLocked(
-                        pkg, info.uid, info.versionCode, processName);
-                pkgList.put(pkg, state);
-                if (state != baseProcessTracker) {
-                    state.makeActive();
+                holder.state = tracker.getProcessStateLocked(
+                        pkg, info.uid, versionCode, processName);
+                pkgList.put(pkg, holder);
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
             } else {
-                pkgList.put(pkg, null);
+                pkgList.put(pkg, holder);
             }
             return true;
         }
@@ -615,23 +616,26 @@
                     tracker.getMemFactorLocked(), now, pkgList);
             if (N != 1) {
                 for (int i=0; i<N; i++) {
-                    ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                    if (ps != null && ps != baseProcessTracker) {
-                        ps.makeInactive();
+                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                    if (holder.state != null && holder.state != baseProcessTracker) {
+                        holder.state.makeInactive();
                     }
 
                 }
                 pkgList.clear();
                 ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
                         info.packageName, info.uid, info.versionCode, processName);
-                pkgList.put(info.packageName, ps);
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        info.versionCode);
+                holder.state = ps;
+                pkgList.put(info.packageName, holder);
                 if (ps != baseProcessTracker) {
                     ps.makeActive();
                 }
             }
         } else if (N != 1) {
             pkgList.clear();
-            pkgList.put(info.packageName, null);
+            pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.versionCode));
         }
     }
     
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 14f3ef9..7ec14c2 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -171,10 +171,17 @@
         return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
     }
 
+    public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
+    }
+
     public boolean shouldWriteNowLocked(long now) {
         if (now > (mLastWriteTime+WRITE_PERIOD)) {
             if (SystemClock.elapsedRealtime()
-                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) {
+                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
+                    SystemClock.uptimeMillis()
+                    > (mProcessStats.mTimePeriodStartUptime+ProcessStats.COMMIT_UPTIME_PERIOD)) {
                 mCommitPending = true;
             }
             return true;
@@ -212,6 +219,7 @@
             if (mPendingWrite == null || !mPendingWriteCommitted) {
                 mPendingWrite = Parcel.obtain();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                mProcessStats.mTimePeriodEndUptime = now;
                 if (commit) {
                     mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
                 }
@@ -439,8 +447,10 @@
         mWriteLock.lock();
         try {
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
             }
             if (historic != null) {
                 ArrayList<String> files = getCommittedFiles(0, false, true);
@@ -470,8 +480,10 @@
             Parcel current = Parcel.obtain();
             long curTime;
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
                 curTime = mProcessStats.mTimePeriodEndRealtime
                         - mProcessStats.mTimePeriodStartRealtime;
             }
@@ -568,8 +580,8 @@
     static private void dumpHelp(PrintWriter pw) {
         pw.println("Process stats (procstats) dump options:");
         pw.println("    [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
-        pw.println("    [--details] [--full-details] [--current] [--hours] [--active]");
-        pw.println("    [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
+        pw.println("    [--details] [--full-details] [--current] [--hours N] [--last N]");
+        pw.println("    [--active] [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
         pw.println("  --checkin: perform a checkin: print and delete old committed states.");
         pw.println("  --c: print only state in checkin format.");
         pw.println("  --csv: output data suitable for putting in a spreadsheet.");
@@ -581,6 +593,7 @@
         pw.println("  --full-details: dump all timing and active state details.");
         pw.println("  --current: only dump current state.");
         pw.println("  --hours: aggregate over about N last hours.");
+        pw.println("  --last: only show the last committed stats at index N (starting at 1).");
         pw.println("  --active: only show currently active processes/services.");
         pw.println("  --commit: commit current stats to disk and reset to start new stats.");
         pw.println("  --reset: reset current stats, without committing.");
@@ -621,6 +634,7 @@
         boolean dumpFullDetails = false;
         boolean dumpAll = false;
         int aggregateHours = 0;
+        int lastIndex = 0;
         boolean activeOnly = false;
         String reqPackage = null;
         boolean csvSepScreenStats = false;
@@ -705,6 +719,20 @@
                         dumpHelp(pw);
                         return;
                     }
+                } else if ("--last".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --last");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    try {
+                        lastIndex = Integer.parseInt(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: --last argument not an int -- " + args[i]);
+                        dumpHelp(pw);
+                        return;
+                    }
                 } else if ("--active".equals(arg)) {
                     activeOnly = true;
                     currentOnly = true;
@@ -818,6 +846,43 @@
             dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
                     dumpDetails, dumpFullDetails, dumpAll, activeOnly);
             return;
+        } else if (lastIndex > 0) {
+            pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
+            ArrayList<String> files = getCommittedFiles(0, false, true);
+            if (lastIndex >= files.size()) {
+                pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
+                return;
+            }
+            AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
+            ProcessStats processStats = new ProcessStats(false);
+            readLocked(processStats, file);
+            if (processStats.mReadError != null) {
+                if (isCheckin || isCompact) pw.print("err,");
+                pw.print("Failure reading "); pw.print(files.get(lastIndex));
+                pw.print("; "); pw.println(processStats.mReadError);
+                return;
+            }
+            String fileStr = file.getBaseFile().getPath();
+            boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
+            if (isCheckin || isCompact) {
+                // Don't really need to lock because we uniquely own this object.
+                processStats.dumpCheckinLocked(pw, reqPackage);
+            } else {
+                pw.print("COMMITTED STATS FROM ");
+                pw.print(processStats.mTimePeriodStartClockStr);
+                if (checkedIn) pw.print(" (checked in)");
+                pw.println(":");
+                if (dumpDetails || dumpFullDetails) {
+                    processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
+                            activeOnly);
+                    if (dumpAll) {
+                        pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                    }
+                } else {
+                    processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+                }
+            }
+            return;
         }
 
         boolean sepNeeded = false;
@@ -859,7 +924,7 @@
                                 // Always dump summary here, dumping all details is just too
                                 // much crud.
                                 if (dumpFullDetails) {
-                                    mProcessStats.dumpLocked(pw, reqPackage, now, false, false,
+                                    processStats.dumpLocked(pw, reqPackage, now, false, false,
                                             activeOnly);
                                 } else {
                                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index e54c95e..f79c026 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -45,6 +45,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A running application service.
@@ -70,9 +71,6 @@
     final String packageName; // the package implementing intent's component
     final String processName; // process where this component wants to run
     final String permission;// permission needed to access service
-    final String baseDir;   // where activity source (resources etc) located
-    final String resDir;   // where public activity source (public resources etc) located
-    final String dataDir;   // where activity data should go
     final boolean exported; // from ServiceInfo.exported
     final Runnable restarter; // used to schedule retries of starting the service
     final long createTime;  // when this service was created
@@ -209,11 +207,13 @@
         }
         long now = SystemClock.uptimeMillis();
         long nowReal = SystemClock.elapsedRealtime();
-        pw.print(prefix); pw.print("baseDir="); pw.println(baseDir);
-        if (!resDir.equals(baseDir)) {
-            pw.print(prefix); pw.print("resDir="); pw.println(resDir);
+        if (appInfo != null) {
+            pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir);
+            if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+                pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir);
+            }
+            pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir);
         }
-        pw.print(prefix); pw.print("dataDir="); pw.println(dataDir);
         pw.print(prefix); pw.print("app="); pw.println(app);
         if (isolatedProc != null) {
             pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc);
@@ -305,9 +305,6 @@
         packageName = sInfo.applicationInfo.packageName;
         processName = sInfo.processName;
         permission = sInfo.permission;
-        baseDir = sInfo.applicationInfo.sourceDir;
-        resDir = sInfo.applicationInfo.publicSourceDir;
-        dataDir = sInfo.applicationInfo.dataDir;
         exported = sInfo.exported;
         this.restarter = restarter;
         createTime = SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index bb289fa..eee7e9e 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
@@ -45,7 +46,7 @@
     static final boolean DEBUG = false;
 
     /** When in slow mode don't write tasks out faster than this */
-    private static final long INTER_TASK_DELAY_MS = 60000;
+    private static final long INTER_TASK_DELAY_MS = 10000;
     private static final long DEBUG_INTER_TASK_DELAY_MS = 5000;
 
     private static final String RECENTS_FILENAME = "_task";
@@ -56,6 +57,9 @@
 
     private static final String TAG_TASK = "task";
 
+    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
+    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+
     private static File sImagesDir;
     private static File sTasksDir;
 
@@ -69,6 +73,7 @@
     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
         sTasksDir = new File(systemDir, TASKS_DIRNAME);
         if (!sTasksDir.exists()) {
+            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
             if (!sTasksDir.mkdir()) {
                 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
             }
@@ -76,6 +81,7 @@
 
         sImagesDir = new File(systemDir, IMAGES_DIRNAME);
         if (!sImagesDir.exists()) {
+            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
             if (!sImagesDir.mkdir()) {
                 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
             }
@@ -141,6 +147,53 @@
         }
     }
 
+    static void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
+            String iconFilename, XmlSerializer out) throws IOException {
+        if (taskDescription != null) {
+            final String label = taskDescription.getLabel();
+            if (label != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
+            }
+            final int colorPrimary = taskDescription.getPrimaryColor();
+            if (colorPrimary != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
+            }
+            final Bitmap icon = taskDescription.getIcon();
+            if (icon != null) {
+                saveImage(icon, iconFilename);
+            }
+        }
+    }
+
+    static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
+        String attrName, String attrValue) {
+        if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+            taskDescription.setLabel(attrValue);
+            return true;
+        } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
+            taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
+            return true;
+        }
+        return false;
+    }
+
+    private String fileToString(File file) {
+        final String newline = System.lineSeparator();
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(file));
+            StringBuffer sb = new StringBuffer((int) file.length() * 2);
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line + newline);
+            }
+            reader.close();
+            return sb.toString();
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Couldn't read file " + file.getName());
+            return null;
+        }
+    }
+
     ArrayList<TaskRecord> restoreTasksLocked() {
         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
@@ -170,22 +223,29 @@
                         if (TAG_TASK.equals(name)) {
                             final TaskRecord task =
                                     TaskRecord.restoreFromXml(in, mStackSupervisor);
-                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task);
+                            if (true || DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
+                                    task);
                             if (task != null) {
+                                task.isPersistable = true;
+                                task.needsPersisting = true;
                                 tasks.add(task);
                                 final int taskId = task.taskId;
                                 recoveredTaskIds.add(taskId);
                                 mStackSupervisor.setNextTaskId(taskId);
+                            } else {
+                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
+                                        fileToString(taskFile));
                             }
                         } else {
-                            Slog.e(TAG, "restoreTasksLocked Unknown xml event=" + event + " name="
-                                    + name);
+                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
+                                    " name=" + name);
                         }
                     }
                     XmlUtils.skipCurrentTag(in);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
+                Slog.e(TAG, "Failing file: " + fileToString(taskFile));
                 deleteFile = true;
             } finally {
                 if (reader != null) {
@@ -195,6 +255,7 @@
                     }
                 }
                 if (!DEBUG && deleteFile) {
+                    if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
                     taskFile.delete();
                 }
             }
@@ -209,7 +270,7 @@
         Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
             @Override
             public int compare(TaskRecord lhs, TaskRecord rhs) {
-                final long diff = lhs.mLastTimeMoved - rhs.mLastTimeMoved;
+                final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
                 if (diff < 0) {
                     return -1;
                 } else if (diff > 0) {
@@ -233,13 +294,13 @@
                 try {
                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
                 } catch (Exception e) {
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Can't parse file=" +
-                            file.getName());
+                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
                     file.delete();
                     continue;
                 }
                 if (!persistentTaskIds.contains(taskId)) {
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
+                    if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
+                            file.getName());
                     file.delete();
                 }
             }
@@ -288,15 +349,18 @@
                 synchronized(mService) {
                     final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
                     persistentTaskIds.clear();
+                    if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
                     for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                         task = tasks.get(taskNdx);
                         if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
                                 task.isPersistable + " needsPersisting=" + task.needsPersisting);
-                        if (task.isPersistable) {
+                        if (task.isPersistable && !task.stack.isHomeStack()) {
+                            if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
                             persistentTaskIds.add(task.taskId);
 
                             if (task.needsPersisting) {
                                 try {
+                                    if (DEBUG) Slog.d(TAG, "Saving task=" + task);
                                     stringWriter = saveToXml(task);
                                     break;
                                 } catch (IOException e) {
@@ -305,6 +369,8 @@
                                     task.needsPersisting = false;
                                 }
                             }
+                        } else {
+                            if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
                         }
                     }
                 }
@@ -330,6 +396,8 @@
                     // Made it through the entire list and didn't find anything new that needed
                     // persisting.
                     if (!DEBUG) {
+                        if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
+                                persistentTaskIds);
                         removeObsoleteFiles(persistentTaskIds);
                     }
 
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 81a0b36..0bd7ef9 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -17,6 +17,9 @@
 package com.android.server.am;
 
 import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
 
 import android.app.Activity;
@@ -27,7 +30,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Slog;
@@ -54,14 +56,16 @@
     private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
     private static final String ATTR_USERID = "user_id";
     private static final String ATTR_TASKTYPE = "task_type";
-    private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
+    private static final String ATTR_LASTACTIVETIME = "last_active_time";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+    private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
+    private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     final int taskId;       // Unique identifier for this task.
-    final String affinity;  // The affinity name for this task, or null.
+    String affinity;        // The affinity name for this task, or null.
     final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
     final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
     Intent intent;          // The original intent that started the task.
@@ -107,9 +111,17 @@
     /** True if persistable, has changed, and has not yet been persisted */
     boolean needsPersisting = false;
 
-    /** Launch the home activity when leaving this task. Will be false for tasks that are not on
-     * Display.DEFAULT_DISPLAY. */
-    boolean mOnTopOfHome = false;
+    /** Indication of what to run next when task exits. Use ActivityRecord types.
+     * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the
+     * task stack. */
+    private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE;
+
+    /** If original intent did not allow relinquishing task identity, save that information */
+    boolean mNeverRelinquishIdentity = true;
+
+    // Used in the unique case where we are clearing the task in order to reuse it. In that case we
+    // do not want to delete the stack when the task goes empty.
+    boolean mReuseTask = false;
 
     final ActivityManagerService mService;
 
@@ -117,7 +129,6 @@
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
         taskId = _taskId;
-        affinity = info.taskAffinity;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
         setIntent(_intent, info);
@@ -126,9 +137,10 @@
 
     TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
             String _affinity, ComponentName _realActivity, ComponentName _origActivity,
-            boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
-            int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long lastTimeMoved) {
+            boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
+            String _lastDescription, ArrayList<ActivityRecord> activities, long _lastActiveTime,
+            long lastTimeMoved, boolean neverRelinquishIdentity,
+            ActivityManager.TaskDescription _lastTaskDescription) {
         mService = service;
         taskId = _taskId;
         intent = _intent;
@@ -141,11 +153,14 @@
         rootWasReset = _rootWasReset;
         askedCompatMode = _askedCompatMode;
         taskType = _taskType;
-        mOnTopOfHome = _onTopOfHome;
+        mTaskToReturnTo = HOME_ACTIVITY_TYPE;
         userId = _userId;
+        lastActiveTime = _lastActiveTime;
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
+        mNeverRelinquishIdentity = neverRelinquishIdentity;
+        lastTaskDescription = _lastTaskDescription;
     }
 
     void touchActiveTime() {
@@ -157,6 +172,14 @@
     }
 
     void setIntent(Intent _intent, ActivityInfo info) {
+        if (intent == null) {
+            mNeverRelinquishIdentity =
+                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+        } else if (mNeverRelinquishIdentity) {
+            return;
+        }
+
+        affinity = info.taskAffinity;
         stringName = null;
 
         if (info.targetActivity == null) {
@@ -209,6 +232,14 @@
         }
     }
 
+    void setTaskToReturnTo(int taskToReturnTo) {
+        mTaskToReturnTo = taskToReturnTo;
+    }
+
+    int getTaskToReturnTo() {
+        return mTaskToReturnTo;
+    }
+
     void disposeThumbnail() {
         super.disposeThumbnail();
         for (int i=mActivities.size()-1; i>=0; i--) {
@@ -271,6 +302,11 @@
                 foundFront = true;
             }
         }
+        if (!foundFront && numActivities > 0) {
+            // All activities of this task are finishing. As we ought to have a frontOfTask
+            // activity, make the bottom activity front.
+            mActivities.get(0).frontOfTask = true;
+        }
     }
 
     /**
@@ -282,6 +318,7 @@
 
         mActivities.remove(newTop);
         mActivities.add(newTop);
+        updateEffectiveIntent();
 
         setFrontOfTask();
     }
@@ -311,6 +348,7 @@
             r.mActivityType = taskType;
         }
         mActivities.add(index, r);
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -325,7 +363,11 @@
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
-        return mActivities.size() == 0;
+        if (mActivities.isEmpty()) {
+            return !mReuseTask;
+        }
+        updateEffectiveIntent();
+        return false;
     }
 
     boolean autoRemoveFromRecents() {
@@ -366,7 +408,9 @@
      * Completely remove all activities associated with an existing task.
      */
     final void performClearTaskLocked() {
+        mReuseTask = true;
         performClearTaskAtIndexLocked(0);
+        mReuseTask = false;
     }
 
     /**
@@ -486,11 +530,15 @@
     }
 
     boolean isHomeTask() {
-        return taskType == ActivityRecord.HOME_ACTIVITY_TYPE;
+        return taskType == HOME_ACTIVITY_TYPE;
     }
 
     boolean isApplicationTask() {
-        return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+        return taskType == APPLICATION_ACTIVITY_TYPE;
+    }
+
+    boolean isOverHomeStack() {
+        return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
     }
 
     public TaskAccessInfo getTaskAccessInfoLocked() {
@@ -579,12 +627,19 @@
         // utility activities.
         int activityNdx;
         final int numActivities = mActivities.size();
+        final boolean relinquish = numActivities == 0 ? false :
+                (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
         for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
                 ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
+            if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                // This will be the top activity for determining taskDescription. Pre-inc to
+                // overcome initial decrement below.
+                ++activityNdx;
+                break;
+            }
             if (r.intent != null &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                            != 0) {
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 break;
             }
         }
@@ -615,6 +670,27 @@
         }
     }
 
+    int findEffectiveRootIndex() {
+        int activityNdx;
+        final int topActivityNdx = mActivities.size() - 1;
+        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                break;
+            }
+        }
+        return activityNdx;
+    }
+
+    void updateEffectiveIntent() {
+        final int effectiveRootIndex = findEffectiveRootIndex();
+        final ActivityRecord r = mActivities.get(effectiveRootIndex);
+        setIntent(r.intent, r.info);
+    }
+
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         Slog.i(TAG, "Saving task=" + this);
 
@@ -632,12 +708,18 @@
         out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
         out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
-        out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
+        out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
 
+        if (lastTaskDescription != null) {
+            TaskPersister.saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
+                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
+        }
+
         if (affinityIntent != null) {
             out.startTag(null, TAG_AFFINITYINTENT);
             affinityIntent.saveToXml(out);
@@ -652,8 +734,9 @@
         final int numActivities = activities.size();
         for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
-            if (!r.isPersistable() || (activityNdx > 0 &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0)) {
+            if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
+                    ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
+                            activityNdx > 0) {
                 // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
                 break;
             }
@@ -679,12 +762,14 @@
         boolean rootHasReset = false;
         boolean askedCompatMode = false;
         int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-        boolean onTopOfHome = true;
         int userId = 0;
         String lastDescription = null;
+        long lastActiveTime = -1;
         long lastTimeOnTop = 0;
+        boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
+        ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
@@ -707,12 +792,17 @@
                 userId = Integer.valueOf(attrValue);
             } else if (ATTR_TASKTYPE.equals(attrName)) {
                 taskType = Integer.valueOf(attrValue);
-            } else if (ATTR_ONTOPOFHOME.equals(attrName)) {
-                onTopOfHome = Boolean.valueOf(attrValue);
+            } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
+                lastActiveTime = Long.valueOf(attrValue);
             } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
                 lastTimeOnTop = Long.valueOf(attrValue);
+            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
+                neverRelinquishIdentity = Boolean.valueOf(attrValue);
+            } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+                    attrValue)) {
+                // Completed in TaskPersister.readTaskDescriptionAttribute()
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -744,10 +834,15 @@
             }
         }
 
+        if (lastActiveTime >= 0) {
+            taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime));
+        }
+
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
-                askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
-                lastTimeOnTop);
+                askedCompatMode, taskType, userId, lastDescription, activities, lastActiveTime,
+                lastTimeOnTop, neverRelinquishIdentity, taskDescription);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
@@ -766,7 +861,7 @@
                     pw.print(" userId="); pw.print(userId);
                     pw.print(" taskType="); pw.print(taskType);
                     pw.print(" numFullscreen="); pw.print(numFullscreen);
-                    pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
+                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
         }
         if (affinity != null) {
             pw.print(prefix); pw.print("affinity="); pw.println(affinity);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b03c247..1332898 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -78,6 +78,6 @@
 
     public String name() {
         return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" +
-                networkInfo.getSubtypeName() + ")]";
+                networkInfo.getSubtypeName() + ") - " + network.toString() + "]";
     }
 }
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..a6485e9 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -172,11 +172,8 @@
             throw new IllegalArgumentException("You must pass a valid uri and observer");
         }
 
-        final int callingUser = UserHandle.getCallingUserId();
-        if (callingUser != userHandle) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "no permission to observe other users' provider view");
-        }
+        enforceCrossUserPermission(userHandle,
+                "no permission to observe other users' provider view");
 
         if (userHandle < 0) {
             if (userHandle == UserHandle.USER_CURRENT) {
@@ -230,7 +227,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");
         }
 
@@ -346,7 +343,15 @@
      * @param request The request object. Validation of this object is done by its builder.
      */
     public void sync(SyncRequest request) {
-        int userId = UserHandle.getCallingUserId();
+        syncAsUser(request, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public void syncAsUser(SyncRequest request, int userId) {
+        enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId);
         int callerUid = Binder.getCallingUid();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
@@ -399,11 +404,30 @@
      */
     @Override
     public void cancelSync(Account account, String authority, ComponentName cname) {
+        cancelSyncAsUser(account, authority, cname, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * Clear all scheduled sync operations that match the uri and cancel the active sync
+     * if they match the authority and account, if they are present.
+     *
+     * <p> If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param account filter the pending and active syncs to cancel using this account, or null.
+     * @param authority filter the pending and active syncs to cancel using this authority, or
+     * null.
+     * @param userId the user id for which to cancel sync operations.
+     * @param cname cancel syncs running on this service, or null for provider/account.
+     */
+    @Override
+    public void cancelSyncAsUser(Account account, String authority, ComponentName cname,
+            int userId) {
         if (authority != null && authority.length() == 0) {
             throw new IllegalArgumentException("Authority must be non-empty");
         }
-
-        int userId = UserHandle.getCallingUserId();
+        enforceCrossUserPermission(userId,
+                "no permission to modify the sync settings for user " + userId);
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
@@ -456,9 +480,23 @@
      */
     @Override
     public SyncAdapterType[] getSyncAdapterTypes() {
+        return getSyncAdapterTypesAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * Get information about the SyncAdapters that are known to the system for a particular user.
+     *
+     * <p> If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @return an array of SyncAdapters that have registered with the system
+     */
+    @Override
+    public SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read sync settings for user " + userId);
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
-        final int userId = UserHandle.getCallingUserId();
         final long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -470,10 +508,20 @@
 
     @Override
     public boolean getSyncAutomatically(Account account, String providerName) {
+        return getSyncAutomaticallyAsUser(account, providerName, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Override
+    public boolean getSyncAutomaticallyAsUser(Account account, String providerName, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -588,9 +636,18 @@
     }
 
     public int getIsSyncable(Account account, String providerName) {
+        return getIsSyncableAsUser(account, providerName, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public int getIsSyncableAsUser(Account account, String providerName, int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
         long identityToken = clearCallingIdentity();
         try {
@@ -627,10 +684,20 @@
 
     @Override
     public boolean getMasterSyncAutomatically() {
+        return getMasterSyncAutomaticallyAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    @Override
+    public boolean getMasterSyncAutomaticallyAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -679,10 +746,19 @@
     }
 
     public List<SyncInfo> getCurrentSyncs() {
+        return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync settings for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
 
-        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
@@ -692,13 +768,24 @@
     }
 
     public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
+        return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
+    }
+
+    /**
+     * If the user id supplied is different to the calling user, the caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission.
+     */
+    public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+            ComponentName cname, int userId) {
         if (TextUtils.isEmpty(authority)) {
             throw new IllegalArgumentException("Authority must not be empty");
         }
+
+        enforceCrossUserPermission(userId,
+                "no permission to read the sync stats for user " + userId);
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
 
-        int userId = UserHandle.getCallingUserId();
         int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
@@ -772,6 +859,21 @@
     }
 
     /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
+     * permission, if the userHandle is not for the caller.
+     *
+     * @param userHandle the user handle of the user we want to act on behalf of.
+     * @param message the message to log on security exception.
+     */
+    private void enforceCrossUserPermission(int userHandle, String message) {
+        final int callingUser = UserHandle.getCallingUserId();
+        if (callingUser != userHandle) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+        }
+    }
+
+    /**
      * Hide this class since it is not part of api,
      * but current unittest framework requires it to be public
      * @hide
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index a77443d..c7f4f6a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -119,7 +119,7 @@
     public int height;
 
     /**
-     * The refresh rate of the display.
+     * The refresh rate of the display, in frames per second.
      */
     public float refreshRate;
 
@@ -144,6 +144,20 @@
     public float yDpi;
 
     /**
+     * This is a positive value indicating the phase offset of the VSYNC events provided by
+     * Choreographer relative to the display refresh.  For example, if Choreographer reports
+     * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos).
+     */
+    public long appVsyncOffsetNanos;
+
+    /**
+     * This is how far in advance a buffer must be queued for presentation at
+     * a given time.  If you want a buffer to appear on the screen at
+     * time N, you must submit the buffer before (N - bufferDeadlineNanos).
+     */
+    public long presentationDeadlineNanos;
+
+    /**
      * Display flags.
      */
     public int flags;
@@ -219,6 +233,8 @@
                 && densityDpi == other.densityDpi
                 && xDpi == other.xDpi
                 && yDpi == other.yDpi
+                && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+                && presentationDeadlineNanos == other.presentationDeadlineNanos
                 && flags == other.flags
                 && touch == other.touch
                 && rotation == other.rotation
@@ -242,6 +258,8 @@
         densityDpi = other.densityDpi;
         xDpi = other.xDpi;
         yDpi = other.yDpi;
+        appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+        presentationDeadlineNanos = other.presentationDeadlineNanos;
         flags = other.flags;
         touch = other.touch;
         rotation = other.rotation;
@@ -261,6 +279,8 @@
         sb.append(", ").append(refreshRate).append(" fps, ");
         sb.append("density ").append(densityDpi);
         sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
+        sb.append(", appVsyncOff ").append(appVsyncOffsetNanos);
+        sb.append(", presDeadline ").append(presentationDeadlineNanos);
         sb.append(", touch ").append(touchToString(touch));
         sb.append(", rotation ").append(rotation);
         sb.append(", type ").append(Display.typeToString(type));
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..098537c 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 {
@@ -158,6 +161,8 @@
                 mInfo.width = mPhys.width;
                 mInfo.height = mPhys.height;
                 mInfo.refreshRate = mPhys.refreshRate;
+                mInfo.appVsyncOffsetNanos = mPhys.appVsyncOffsetNanos;
+                mInfo.presentationDeadlineNanos = mPhys.presentationDeadlineNanos;
                 mInfo.state = mState;
 
                 // Assume that all built-in displays that have secure output (eg. HDCP) also
@@ -204,11 +209,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/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index d61a35b..ed619d9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -213,6 +213,8 @@
             mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
             mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
             mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
+            mBaseDisplayInfo.appVsyncOffsetNanos = deviceInfo.appVsyncOffsetNanos;
+            mBaseDisplayInfo.presentationDeadlineNanos = deviceInfo.presentationDeadlineNanos;
             mBaseDisplayInfo.state = deviceInfo.state;
             mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width;
             mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index bfd8372c..3b23b6a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -191,6 +191,7 @@
         private final int mWidth;
         private final int mHeight;
         private final float mRefreshRate;
+        private final long mDisplayPresentationDeadlineNanos;
         private final int mDensityDpi;
         private final boolean mSecure;
 
@@ -200,7 +201,7 @@
         private DisplayDeviceInfo mInfo;
 
         public OverlayDisplayDevice(IBinder displayToken, String name,
-                int width, int height, float refreshRate,
+                int width, int height, float refreshRate, long presentationDeadlineNanos,
                 int densityDpi, boolean secure, int state,
                 SurfaceTexture surfaceTexture) {
             super(OverlayDisplayAdapter.this, displayToken);
@@ -208,6 +209,7 @@
             mWidth = width;
             mHeight = height;
             mRefreshRate = refreshRate;
+            mDisplayPresentationDeadlineNanos = presentationDeadlineNanos;
             mDensityDpi = densityDpi;
             mSecure = secure;
             mState = state;
@@ -249,6 +251,8 @@
                 mInfo.densityDpi = mDensityDpi;
                 mInfo.xDpi = mDensityDpi;
                 mInfo.yDpi = mDensityDpi;
+                mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos +
+                        1000000000L / (int) mRefreshRate;   // display's deadline + 1 frame
                 mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
                 if (mSecure) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
@@ -297,12 +301,13 @@
 
         // Called on the UI thread.
         @Override
-        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, int state) {
+        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
+                long presentationDeadlineNanos, int state) {
             synchronized (getSyncRoot()) {
                 IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
                 mDevice = new OverlayDisplayDevice(displayToken, mName,
-                        mWidth, mHeight, refreshRate, mDensityDpi, mSecure,
-                        state, surfaceTexture);
+                        mWidth, mHeight, refreshRate, presentationDeadlineNanos,
+                        mDensityDpi, mSecure, state, surfaceTexture);
 
                 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
             }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index 06891f3..9ca5fda 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -303,7 +303,7 @@
         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
                 int width, int height) {
             mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate,
-                    mDefaultDisplayInfo.state);
+                    mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state);
         }
 
         @Override
@@ -373,7 +373,7 @@
      */
     public interface Listener {
         public void onWindowCreated(SurfaceTexture surfaceTexture,
-                float refreshRate, int state);
+                float refreshRate, long presentationDeadlineNanos, int state);
         public void onWindowDestroyed();
         public void onStateChanged(int state);
     }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 14ef5a9..ec14cf1 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -171,6 +171,7 @@
                 mInfo.densityDpi = mDensityDpi;
                 mInfo.xDpi = mDensityDpi;
                 mInfo.yDpi = mDensityDpi;
+                mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
                 mInfo.flags = 0;
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index cd57941..a05bf2c 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -127,7 +127,7 @@
         pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
         pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
         pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
- 
+
         // Try to dump the controller state.
         if (mDisplayController == null) {
             pw.println("mDisplayController=null");
@@ -729,6 +729,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.refreshRate = mRefreshRate;
+                mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
                 mInfo.flags = mFlags;
                 mInfo.type = Display.TYPE_WIFI;
                 mInfo.address = mAddress;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 8968da3..a35e2ba 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.dreams;
 
+import static android.Manifest.permission.BIND_DREAM_SERVICE;
+
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
@@ -29,6 +31,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -103,6 +106,9 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+            if (Build.IS_DEBUGGABLE) {
+                SystemProperties.addChangeCallback(mSystemPropertiesChanged);
+            }
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -127,6 +133,7 @@
         pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
         pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
         pw.println("mCurrentDreamDozeHardware=" + mCurrentDreamDozeHardware);
+        pw.println("getDozeComponent()=" + getDozeComponent());
         pw.println();
 
         DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
@@ -189,7 +196,7 @@
 
     private void startDreamInternal(boolean doze) {
         final int userId = ActivityManager.getCurrentUser();
-        final ComponentName dream = doze ? getDozeComponent() : chooseDreamForUser(userId);
+        final ComponentName dream = chooseDreamForUser(doze, userId);
         if (dream != null) {
             synchronized (mLock) {
                 startDreamLocked(dream, false /*isTest*/, doze, userId);
@@ -241,11 +248,31 @@
         }
     }
 
-    private ComponentName chooseDreamForUser(int userId) {
+    private ComponentName chooseDreamForUser(boolean doze, int userId) {
+        if (doze) {
+            ComponentName dozeComponent = getDozeComponent();
+            return validateDream(dozeComponent) ? dozeComponent : null;
+        }
         ComponentName[] dreams = getDreamComponentsForUser(userId);
         return dreams != null && dreams.length != 0 ? dreams[0] : null;
     }
 
+    private boolean validateDream(ComponentName component) {
+        if (component == null) return false;
+        final ServiceInfo serviceInfo = getServiceInfo(component);
+        if (serviceInfo == null) {
+            Slog.w(TAG, "Dream " + component + " does not exist");
+            return false;
+        } else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.L
+                && !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
+            Slog.w(TAG, "Dream " + component
+                    + " is not available because its manifest is missing the " + BIND_DREAM_SERVICE
+                    + " permission on the dream service declaration.");
+            return false;
+        }
+        return true;
+    }
+
     private ComponentName[] getDreamComponentsForUser(int userId) {
         String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_COMPONENTS,
@@ -256,10 +283,8 @@
         List<ComponentName> validComponents = new ArrayList<ComponentName>();
         if (components != null) {
             for (ComponentName component : components) {
-                if (serviceExists(component)) {
+                if (validateDream(component)) {
                     validComponents.add(component);
-                } else {
-                    Slog.w(TAG, "Dream " + component + " does not exist");
                 }
             }
         }
@@ -303,11 +328,11 @@
         return TextUtils.isEmpty(name) ? null : ComponentName.unflattenFromString(name);
     }
 
-    private boolean serviceExists(ComponentName name) {
+    private ServiceInfo getServiceInfo(ComponentName name) {
         try {
-            return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null;
+            return name != null ? mContext.getPackageManager().getServiceInfo(name, 0) : null;
         } catch (NameNotFoundException e) {
-            return false;
+            return null;
         }
     }
 
@@ -653,4 +678,18 @@
             }
         }
     }
+
+    private final Runnable mSystemPropertiesChanged = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "System properties changed");
+            synchronized(mLock) {
+                if (mCurrentDreamName != null && mCurrentDreamCanDoze
+                        && !mCurrentDreamName.equals(getDozeComponent())) {
+                    // may have updated the doze component, wake up
+                    stopDreamLocked();
+                }
+            }
+        }
+    };
 }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
new file mode 100644
index 0000000..2941574
--- /dev/null
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -0,0 +1,328 @@
+/**
+ * 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.fingerprint;
+
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.service.fingerprint.FingerprintManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import android.service.fingerprint.FingerprintUtils;
+import android.service.fingerprint.IFingerprintService;
+import android.service.fingerprint.IFingerprintServiceReceiver;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A service to manage multiple clients that want to access the fingerprint HAL API.
+ * The service is responsible for maintaining a list of clients and dispatching all
+ * fingerprint -related events.
+ *
+ * @hide
+ */
+public class FingerprintService extends SystemService {
+    private final String TAG = "FingerprintService";
+    private static final boolean DEBUG = true;
+    private ArrayMap<IBinder, ClientData> mClients = new ArrayMap<IBinder, ClientData>();
+
+    private static final int MSG_NOTIFY = 10;
+
+    Handler mHandler = new Handler() {
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_NOTIFY:
+                    handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj);
+                    break;
+
+                default:
+                    Slog.w(TAG, "Unknown message:" + msg.what);
+            }
+        }
+    };
+    private Context mContext;
+
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_LISTENING = 1;
+    private static final int STATE_ENROLLING = 2;
+    private static final int STATE_REMOVING = 3;
+    private static final long MS_PER_SEC = 1000;
+    public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
+    public static final String ENROLL_FINGERPRINT = "android.permission.ENROLL_FINGERPRINT";
+
+    private static final class ClientData {
+        public IFingerprintServiceReceiver receiver;
+        int state;
+        int userId;
+        public TokenWatcher tokenWatcher;
+        IBinder getToken() { return tokenWatcher.getToken(); }
+    }
+
+    private class TokenWatcher implements IBinder.DeathRecipient {
+        WeakReference<IBinder> token;
+
+        TokenWatcher(IBinder token) {
+            this.token = new WeakReference<IBinder>(token);
+        }
+
+        IBinder getToken() { return token.get(); }
+        public void binderDied() {
+            mClients.remove(token);
+            this.token = null;
+        }
+
+        protected void finalize() throws Throwable {
+            try {
+                if (token != null) {
+                    if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token);
+                    mClients.remove(token);
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+
+    public FingerprintService(Context context) {
+        super(context);
+        mContext = context;
+        nativeInit(this);
+    }
+
+    // TODO: Move these into separate process
+    // JNI methods to communicate from FingerprintManagerService to HAL
+    native int nativeEnroll(int timeout);
+    native int nativeEnrollCancel();
+    native int nativeRemove(int fingerprintId);
+    native int nativeOpenHal();
+    native int nativeCloseHal();
+    native void nativeInit(FingerprintService service);
+
+    // JNI methods for communicating from HAL to clients
+    void notify(int msg, int arg1, int arg2) {
+        mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget();
+    }
+
+    void handleNotify(int msg, int arg1, int arg2) {
+        Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")");
+        for (int i = 0; i < mClients.size(); i++) {
+            ClientData clientData = mClients.valueAt(i);
+            if (clientData == null || clientData.receiver == null) {
+                if (DEBUG) Slog.v(TAG, "clientData at " + i + " is invalid!!");
+                continue;
+            }
+            switch (msg) {
+                case FingerprintManager.FINGERPRINT_ERROR: {
+                    final int error = arg1;
+                    try {
+                        clientData.receiver.onError(error);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "can't send message to client. Did it die?", e);
+                        mClients.remove(mClients.keyAt(i));
+                    }
+                }
+                break;
+                case FingerprintManager.FINGERPRINT_ACQUIRED: {
+                    final int acquireInfo = arg1;
+                    try {
+                        clientData.receiver.onAcquired(acquireInfo);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "can't send message to client. Did it die?", e);
+                        mClients.remove(mClients.keyAt(i));
+                    }
+                    break;
+                }
+                case FingerprintManager.FINGERPRINT_PROCESSED: {
+                    final int fingerId = arg1;
+                    try {
+                        clientData.receiver.onProcessed(fingerId);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "can't send message to client. Did it die?", e);
+                        mClients.remove(mClients.keyAt(i));
+                    }
+                    break;
+                }
+                case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: {
+                    final int fingerId = arg1;
+                    final int remaining = arg2;
+                    if (clientData.state == STATE_ENROLLING) {
+                        // Only send enroll updates to clients that are actually enrolling
+                        try {
+                            clientData.receiver.onEnrollResult(fingerId, remaining);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "can't send message to client. Did it die?", e);
+                            mClients.remove(mClients.keyAt(i));
+                        }
+                        // Update the database with new finger id.
+                        // TODO: move to client code (Settings)
+                        if (remaining == 0) {
+                            FingerprintUtils.addFingerprintIdForUser(fingerId,
+                                    mContext.getContentResolver(), clientData.userId);
+                            clientData.state = STATE_IDLE; // Nothing left to do
+                        }
+                    } else {
+                        if (DEBUG) Slog.w(TAG, "Client not enrolling");
+                        break;
+                    }
+                    break;
+                }
+                case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: {
+                    int fingerId = arg1;
+                    if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL");
+                    FingerprintUtils.removeFingerprintIdForUser(fingerId,
+                            mContext.getContentResolver(), clientData.userId);
+                    if (clientData.receiver != null) {
+                        try {
+                            clientData.receiver.onRemoved(fingerId);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "can't send message to client. Did it die?", e);
+                            mClients.remove(mClients.keyAt(i));
+                        }
+                    }
+                    clientData.state = STATE_LISTENING;
+                }
+                break;
+            }
+        }
+    }
+
+    void startEnroll(IBinder token, long timeout, int userId) {
+        ClientData clientData = mClients.get(token);
+        if (clientData != null) {
+            if (clientData.userId != userId) throw new IllegalStateException("Bad user");
+            clientData.state = STATE_ENROLLING;
+            nativeEnroll((int) (timeout / MS_PER_SEC));
+        } else {
+            Slog.w(TAG, "enroll(): No listener registered");
+        }
+    }
+
+    void startEnrollCancel(IBinder token, int userId) {
+        ClientData clientData = mClients.get(token);
+        if (clientData != null) {
+            if (clientData.userId != userId) throw new IllegalStateException("Bad user");
+            clientData.state = STATE_LISTENING;
+            nativeEnrollCancel();
+        } else {
+            Slog.w(TAG, "enrollCancel(): No listener registered");
+        }
+    }
+
+    // Remove all fingerprints for the given user.
+    void startRemove(IBinder token, int fingerId, int userId) {
+        ClientData clientData = mClients.get(token);
+        if (clientData != null) {
+            if (clientData.userId != userId) throw new IllegalStateException("Bad user");
+            clientData.state = STATE_REMOVING;
+            // The fingerprint id will be removed when we get confirmation from the HAL
+            int result = nativeRemove(fingerId);
+            if (result != 0) {
+                Slog.w(TAG, "Error removing fingerprint with id = " + fingerId);
+            }
+        } else {
+            Slog.w(TAG, "remove(" + token + "): No listener registered");
+        }
+    }
+
+    void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId) {
+        if (DEBUG) Slog.v(TAG, "startListening(" + receiver + ")");
+        if (mClients.get(token) == null) {
+            ClientData clientData = new ClientData();
+            clientData.state = STATE_LISTENING;
+            clientData.receiver = receiver;
+            clientData.userId = userId;
+            clientData.tokenWatcher = new TokenWatcher(token);
+            try {
+                token.linkToDeath(clientData.tokenWatcher, 0);
+                mClients.put(token, clientData);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+            }
+        } else {
+            if (DEBUG) Slog.v(TAG, "listener already registered for " + token);
+        }
+    }
+
+    void removeListener(IBinder token, int userId) {
+        if (DEBUG) Slog.v(TAG, "stopListening(" + token + ")");
+        ClientData clientData = mClients.get(token);
+        if (clientData != null) {
+            token.unlinkToDeath(clientData.tokenWatcher, 0);
+            mClients.remove(token);
+        } else {
+            if (DEBUG) Slog.v(TAG, "listener not registered: " + token);
+        }
+        mClients.remove(token);
+    }
+
+    void checkPermission(String permisison) {
+        // TODO
+    }
+
+    private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
+        @Override // Binder call
+        public void enroll(IBinder token, long timeout, int userId) {
+            checkPermission(ENROLL_FINGERPRINT);
+            startEnroll(token, timeout, userId);
+        }
+
+        @Override // Binder call
+        public void enrollCancel(IBinder token,int userId) {
+            checkPermission(ENROLL_FINGERPRINT);
+            startEnrollCancel(token, userId);
+        }
+
+        @Override // Binder call
+        public void remove(IBinder token, int fingerprintId, int userId) {
+            checkPermission(ENROLL_FINGERPRINT); // TODO: Maybe have another permission
+            startRemove(token, fingerprintId, userId);
+        }
+
+        @Override // Binder call
+        public void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId)
+        {
+            checkPermission(USE_FINGERPRINT);
+            addListener(token, receiver, userId);
+        }
+
+        @Override // Binder call
+        public void stopListening(IBinder token, int userId) {
+            checkPermission(USE_FINGERPRINT);
+            removeListener(token, userId);
+        }
+    }
+
+    @Override
+    public void onStart() {
+       publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+       nativeOpenHal();
+    }
+
+}
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
new file mode 100644
index 0000000..8de6763
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -0,0 +1,120 @@
+/*
+ * 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.annotation.Nullable;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Handles CEC command &lt;Active Source&gt;.
+ * <p>
+ * Used by feature actions that need to handle the command in their flow. Only for TV
+ * local device.
+ */
+final class ActiveSourceHandler {
+    private static final String TAG = "ActiveSourceHandler";
+
+    private final HdmiCecLocalDeviceTv mSource;
+    private final HdmiControlService mService;
+    @Nullable
+    private final IHdmiControlCallback mCallback;
+
+    static ActiveSourceHandler create(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
+        if (source == null) {
+            Slog.e(TAG, "Wrong arguments");
+            return null;
+        }
+        return new ActiveSourceHandler(source, callback);
+    }
+
+    private ActiveSourceHandler(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
+        mSource = source;
+        mService = mSource.getService();
+        mCallback = callback;
+    }
+
+    /**
+     * Handles the incoming active source command.
+     *
+     * @param activeAddress logical address of the device to be the active source
+     * @param activePath routing path of the device to be the active source
+     */
+    void process(int activeAddress, int activePath) {
+        // Seq #17
+        HdmiCecLocalDeviceTv tv = mSource;
+        if (getSourcePath() == activePath && tv.getActiveSource() == getSourceAddress()) {
+            invokeCallback(HdmiCec.RESULT_SUCCESS);
+            return;
+        }
+        HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
+        if (device == null) {
+            // "New device action" initiated by <Active Source> does not require
+            // "Routing change action".
+            tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath, false));
+        }
+
+        int currentActive = tv.getActiveSource();
+        int currentPath = tv.getActivePath();
+        if (!tv.isInPresetInstallationMode()) {
+            tv.updateActiveSource(activeAddress, activePath);
+            if (currentActive != activeAddress && currentPath != activePath) {
+                tv.updateActivePortId(mService.pathToPortId(activePath));
+            }
+            invokeCallback(HdmiCec.RESULT_SUCCESS);
+        } else {
+            // TV is in a mode that should keep its current source/input from
+            // being changed for its operation. Reclaim the active source
+            // or switch the port back to the one used for the current mode.
+            if (currentActive == getSourceAddress()) {
+                HdmiCecMessage activeSource =
+                        HdmiCecMessageBuilder.buildActiveSource(currentActive, currentPath);
+                mService.sendCecCommand(activeSource);
+                tv.updateActiveSource(currentActive, currentPath);
+                invokeCallback(HdmiCec.RESULT_SUCCESS);
+            } else {
+                HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
+                        getSourceAddress(), activePath, currentPath);
+                mService.sendCecCommand(routingChange);
+                tv.addAndStartAction(new RoutingControlAction(tv, currentPath, mCallback));
+            }
+        }
+    }
+
+    private final int getSourceAddress() {
+        return mSource.getDeviceInfo().getLogicalAddress();
+    }
+
+    private final int getSourcePath() {
+        return mSource.getDeviceInfo().getPhysicalAddress();
+    }
+
+    private void invokeCallback(int result) {
+        if (mCallback == null) {
+            return;
+        }
+        try {
+            mCallback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Callback failed:" + e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index 579f89f..02f088f 100644
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -55,9 +55,6 @@
 
     private static final int DEVICE_POLLING_RETRY = 1;
 
-    // TODO: Move this to common place
-    private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
-
     /**
      * Interface used to report result of device discovery.
      */
@@ -75,7 +72,7 @@
     private static final class DeviceInfo {
         private final int mLogicalAddress;
 
-        private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+        private int mPhysicalAddress = HdmiConstants.INVALID_PHYSICAL_ADDRESS;
         private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
         private String mDisplayName = "";
         private int mDeviceType = HdmiCec.DEVICE_INACTIVE;
@@ -95,14 +92,12 @@
     private int mProcessedDeviceCount = 0;
 
     /**
-     * @Constructor
+     * Constructor.
      *
-     * @param service
-     * @param sourceAddress
+     * @param source an instance of {@link HdmiCecLocalDevice}.
      */
-    DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
-            DeviceDiscoveryCallback callback) {
-        super(service, sourceAddress);
+    DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
+        super(source);
         mCallback = Preconditions.checkNotNull(callback);
     }
 
@@ -111,7 +106,7 @@
         mDevices.clear();
         mState = STATE_WAITING_FOR_DEVICE_POLLING;
 
-        mService.pollDevices(new DevicePollingCallback() {
+        pollDevices(new DevicePollingCallback() {
             @Override
             public void onPollingFinished(List<Integer> ackedAddress) {
                 if (ackedAddress.isEmpty()) {
@@ -124,7 +119,8 @@
                 allocateDevices(ackedAddress);
                 startPhysicalAddressStage();
             }
-        }, DEVICE_POLLING_RETRY);
+        }, HdmiConstants.POLL_ITERATION_REVERSE_ORDER
+            | HdmiConstants.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY);
         return true;
     }
 
@@ -154,7 +150,12 @@
         }
 
         mActionTimer.clearTimerMessage();
-        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
+
+        // Check cache first and send request if not exist.
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
+            return;
+        }
+        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
         addTimer(mState, TIMEOUT_MS);
     }
 
@@ -173,7 +174,11 @@
         }
 
         mActionTimer.clearTimerMessage();
-        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_SET_OSD_NAME)) {
+            return;
+        }
+        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
         addTimer(mState, TIMEOUT_MS);
     }
 
@@ -193,10 +198,24 @@
         }
 
         mActionTimer.clearTimerMessage();
-        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) {
+            return;
+        }
+        sendCommand(
+                HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
         addTimer(mState, TIMEOUT_MS);
     }
 
+    private boolean mayProcessMessageIfCached(int address, int opcode) {
+        HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
+        if (message != null) {
+            processCommand(message);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     boolean processCommand(HdmiCecMessage cmd) {
         switch (mState) {
@@ -237,7 +256,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 +304,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/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
index 63c2182..51df473 100644
--- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
@@ -16,9 +16,10 @@
  * limitations under the License.
  */
 
-import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.HdmiCec;
 import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -40,18 +41,18 @@
     private final int mTargetAddress;
     private final IHdmiControlCallback mCallback;
 
-    static DevicePowerStatusAction create(HdmiControlService service, int sourceAddress,
+    static DevicePowerStatusAction create(HdmiCecLocalDevice source,
             int targetAddress, IHdmiControlCallback callback) {
-        if (service == null || callback == null) {
+        if (source == null || callback == null) {
             Slog.e(TAG, "Wrong arguments");
             return null;
         }
-        return new DevicePowerStatusAction(service, sourceAddress, targetAddress, callback);
+        return new DevicePowerStatusAction(source, targetAddress, callback);
     }
 
-    private DevicePowerStatusAction(HdmiControlService service, int sourceAddress,
+    private DevicePowerStatusAction(HdmiCecLocalDevice localDevice,
             int targetAddress, IHdmiControlCallback callback) {
-        super(service, sourceAddress);
+        super(localDevice);
         mTargetAddress = targetAddress;
         mCallback = callback;
     }
@@ -65,8 +66,8 @@
     }
 
     private void queryDevicePowerStatus() {
-        mService.sendCecCommand(
-                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mSourceAddress, mTargetAddress));
+        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                mTargetAddress));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
new file mode 100644
index 0000000..b97350d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -0,0 +1,220 @@
+/*
+ * 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.hardware.hdmi.HdmiTvClient;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Handles an action that selects a logical device as a new active source.
+ *
+ * Triggered by {@link HdmiTvClient}, attempts to select the given target device
+ * for a new active source. It does its best to wake up the target in standby mode
+ * before issuing the command &gt;Set Stream path&lt;.
+ */
+final class DeviceSelectAction extends FeatureAction {
+    private static final String TAG = "DeviceSelect";
+
+    // Time in milliseconds we wait for the device power status to switch to 'Standby'
+    private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000;
+
+    // Time in milliseconds we wait for the device power status to turn to 'On'.
+    private static final int TIMEOUT_POWER_ON_MS = 5 * 1000;
+
+    // Time in milliseconds we wait for <Active Source>.
+    private static final int TIMEOUT_ACTIVE_SOURCE_MS = 20 * 1000;
+
+    // The number of times we try to wake up the target device before we give up
+    // and just send <Set Stream Path>.
+    private static final int LOOP_COUNTER_MAX = 20;
+
+    // State in which we wait for <Report Power Status> to come in response to the command
+    // <Give Device Power Status> we have sent.
+    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
+
+    // State in which we wait for the device power status to switch to 'Standby'.
+    // We wait till the status becomes 'Standby' before we send <Set Stream Path>
+    // to wake up the device again.
+    private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2;
+
+    // State in which we wait for the device power status to switch to 'on'. We wait
+    // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>.
+    private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;
+
+    // State in which we wait for the <Active Source> in response to the command
+    // <Set Stream Path> we have sent. We wait as much as TIMEOUT_ACTIVE_SOURCE_MS
+    // before we give up and mark the action as failure.
+    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 4;
+
+    private final HdmiCecDeviceInfo mTarget;
+    private final IHdmiControlCallback mCallback;
+
+    private int mPowerStatusCounter = 0;
+
+    /**
+     * Constructor.
+     *
+     * @param source {@link HdmiCecLocalDevice} instance
+     * @param target target logical device that will be a new active source
+     * @param callback callback object
+     */
+    public DeviceSelectAction(HdmiCecLocalDeviceTv source,
+            HdmiCecDeviceInfo target, IHdmiControlCallback callback) {
+        super(source);
+        mCallback = callback;
+        mTarget = target;
+    }
+
+    @Override
+    public boolean start() {
+        // TODO: Call the logic that display a banner saying the select action got started.
+        queryDevicePowerStatus();
+        return true;
+    }
+
+    private void queryDevicePowerStatus() {
+        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                getSourceAddress(), mTarget.getLogicalAddress()));
+        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
+        addTimer(mState, TIMEOUT_MS);
+    }
+
+    @Override
+    public boolean processCommand(HdmiCecMessage cmd) {
+        if (cmd.getSource() != mTarget.getLogicalAddress()) {
+            return false;
+        }
+        int opcode = cmd.getOpcode();
+        byte[] params = cmd.getParams();
+
+        switch (mState) {
+            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
+                if (opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS && params.length == 1) {
+                    return handleReportPowerStatus(params[0]);
+                }
+                return false;
+            case STATE_WAIT_FOR_ACTIVE_SOURCE:
+                if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE && params.length == 2) {
+                    int activePath = HdmiUtils.twoBytesToInt(params);
+                    ActiveSourceHandler
+                            .create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
+                            .process(cmd.getSource(), activePath);
+                    finish();
+                    return true;
+                }
+                return false;
+            default:
+                break;
+        }
+        return false;
+    }
+
+    private boolean handleReportPowerStatus(int powerStatus) {
+        // TODO: Check TV's own status which might have been updated during the action.
+        //       If in 'Standby' or 'Transit to standby', remove the banner
+        //       and stop this action. Otherwise, send <Set Stream Path>
+        switch (powerStatus) {
+            case HdmiCec.POWER_STATUS_ON:
+                sendSetStreamPath();
+                return true;
+            case HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY:
+                if (mPowerStatusCounter < 4) {
+                    mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
+                    addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
+                } else {
+                    sendSetStreamPath();
+                }
+                return true;
+            case HdmiCec.POWER_STATUS_STANDBY:
+                if (mPowerStatusCounter == 0) {
+                    turnOnDevice();
+                } else {
+                    sendSetStreamPath();
+                }
+                return true;
+            case HdmiCec.POWER_STATUS_TRANSIENT_TO_ON:
+                if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
+                    mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
+                    addTimer(mState, TIMEOUT_POWER_ON_MS);
+                } else {
+                    sendSetStreamPath();
+                }
+                return true;
+        }
+        return false;
+    }
+
+    private void turnOnDevice() {
+        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+                HdmiConstants.UI_COMMAND_POWER);
+        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+                HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
+        mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
+        addTimer(mState, TIMEOUT_POWER_ON_MS);
+    }
+
+    private void sendSetStreamPath() {
+        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
+                getSourceAddress(), mTarget.getPhysicalAddress()));
+        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
+        addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
+    }
+
+    @Override
+    public void handleTimerEvent(int timeoutState) {
+        if (mState != timeoutState) {
+            Slog.w(TAG, "Timer in a wrong state. Ignored.");
+            return;
+        }
+        switch (mState) {
+            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
+                sendSetStreamPath();
+                break;
+            case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
+            case STATE_WAIT_FOR_DEVICE_POWER_ON:
+                mPowerStatusCounter++;
+                queryDevicePowerStatus();
+                break;
+            case STATE_WAIT_FOR_ACTIVE_SOURCE:
+                // TODO: Remove the banner
+                //       Display banner "Communication failed. Please check your cable or connection"
+                invokeCallback(HdmiCec.RESULT_TIMEOUT);
+                finish();
+                break;
+        }
+    }
+
+    private void invokeCallback(int result) {
+        if (mCallback == null) {
+            return;
+        }
+        try {
+            mCallback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Callback failed:" + e);
+        }
+    }
+
+    int getTargetAddress() {
+        return mTarget.getLogicalAddress();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 0ba7773..cf28f05 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -22,6 +22,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import java.util.List;
 
 /**
  * Encapsulates a sequence of CEC/MHL command exchange for a certain feature.
@@ -33,21 +36,21 @@
  * of the object. All the actual action classes inherit FeatureAction.
  *
  * <p>More than one FeatureAction objects can be up and running simultaneously,
- * maintained by {@link HdmiControlService}. Each action is passed a new command
+ * maintained by {@link HdmiCecLocalDevice}. Each action is passed a new command
  * arriving from the bus, and either consumes it if the command is what the action expects,
  * or yields it to other action.
  *
  * Declared as package private, accessed by {@link HdmiControlService} only.
  */
 abstract class FeatureAction {
-
     private static final String TAG = "FeatureAction";
 
     // Timer handler message used for timeout event
     protected static final int MSG_TIMEOUT = 100;
 
-    // Default timeout for the incoming command to arrive in response to a request
-    protected static final int TIMEOUT_MS = 1000;
+    // Default timeout for the incoming command to arrive in response to a request.
+    // TODO: Consider reading this value from configuration to allow customization.
+    protected static final int TIMEOUT_MS = 2000;
 
     // Default state used in common by all the feature actions.
     protected static final int STATE_NONE = 0;
@@ -55,19 +58,16 @@
     // Internal state indicating the progress of action.
     protected int mState = STATE_NONE;
 
-    protected final HdmiControlService mService;
-
-    // Logical address of the device for which the feature action is taken. The commands
-    // generated in an action all use this field as source address.
-    protected final int mSourceAddress;
+    private final HdmiControlService mService;
+    private final HdmiCecLocalDevice mSource;
 
     // Timer that manages timeout events.
     protected ActionTimer mActionTimer;
 
-    FeatureAction(HdmiControlService service, int sourceAddress) {
-        mService = service;
-        mSourceAddress = sourceAddress;
-        mActionTimer = createActionTimer(service.getServiceLooper());
+    FeatureAction(HdmiCecLocalDevice source) {
+        mSource = source;
+        mService = mSource.getService();
+        mActionTimer = createActionTimer(mService.getServiceLooper());
     }
 
     @VisibleForTesting
@@ -174,6 +174,42 @@
         mService.sendCecCommand(cmd, callback);
     }
 
+    protected final void addAndStartAction(FeatureAction action) {
+        mSource.addAndStartAction(action);
+    }
+
+    protected final <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
+        return mSource.getActions(clazz);
+    }
+
+    protected final HdmiCecMessageCache getCecMessageCache() {
+        return mSource.getCecMessageCache();
+    }
+
+    /**
+     * Remove the action from the action queue. This is called after the action finishes
+     * its role.
+     *
+     * @param action
+     */
+    protected final void removeAction(FeatureAction action) {
+        mSource.removeAction(action);
+    }
+
+    protected final <T extends FeatureAction> void removeAction(final Class<T> clazz) {
+        mSource.removeActionExcept(clazz, null);
+    }
+
+    protected final <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
+            final FeatureAction exception) {
+        mSource.removeActionExcept(clazz, exception);
+    }
+
+    protected final void pollDevices(DevicePollingCallback callback, int pickStrategy,
+            int retryCount) {
+        mService.pollDevices(callback, pickStrategy, retryCount);
+    }
+
     /**
      * Clean up action's state.
      *
@@ -193,13 +229,30 @@
         removeAction(this);
     }
 
-    /**
-     * Remove the action from the action queue. This is called after the action finishes
-     * its role.
-     *
-     * @param action
-     */
-    private void removeAction(FeatureAction action) {
-        mService.removeAction(action);
+    protected final HdmiCecLocalDevice localDevice() {
+        return mSource;
+    }
+
+    protected final HdmiCecLocalDevicePlayback playback() {
+        return (HdmiCecLocalDevicePlayback) mSource;
+    }
+
+    protected final HdmiCecLocalDeviceTv tv() {
+        return (HdmiCecLocalDeviceTv) mSource;
+    }
+
+    protected final int getSourceAddress() {
+        return mSource.getDeviceInfo().getLogicalAddress();
+    }
+
+    protected final int getSourcePath() {
+        return mSource.getDeviceInfo().getPhysicalAddress();
+    }
+
+    protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
+                getSourceAddress(), targetAddress, uiCommand));
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                getSourceAddress(), targetAddress));
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiAnnotations.java b/services/core/java/com/android/server/hdmi/HdmiAnnotations.java
new file mode 100644
index 0000000..969595d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiAnnotations.java
@@ -0,0 +1,47 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation container for Hdmi control service package.
+ */
+public class HdmiAnnotations {
+    /**
+     * Annotation type to used to mark a method which should be run on service thread.
+     * This annotation should go with {@code assertRunOnServiceThread} used to verify
+     * whether it's called from service thread.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface ServiceThreadOnly {
+    }
+
+    /**
+     * Annotation type to used to mark a method which should be run on io thread.
+     * This annotation should go with {@code assertRunOnIoThread} used to verify
+     * whether it's called from io thread.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface IoThreadOnly {
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index d0b716d..4fbe845 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -17,14 +17,17 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiCec;
-import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiPortInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.Predicate;
+import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
 
 import libcore.util.EmptyArray;
@@ -47,6 +50,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 +81,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;
 
@@ -76,10 +110,6 @@
 
     private HdmiControlService mService;
 
-    // Map-like container of all cec devices including local ones.
-    // A logical address of device is used as key of container.
-    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
-
     // Stores the local CEC devices in the system. Device type is used for key.
     private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
 
@@ -116,45 +146,15 @@
         mNativePtr = nativePtr;
     }
 
-    /**
-     * Perform initialization for each hosted device.
-     *
-     * @param deviceTypes array of device types
-     */
-    void initializeLocalDevices(int[] deviceTypes,
-            HdmiCecLocalDevice.AddressAllocationCallback callback) {
+    @ServiceThreadOnly
+    void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
         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);
+        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.
      *
@@ -165,8 +165,9 @@
      *                         Otherwise, scan address will start from {@code preferredAddress}
      * @param callback callback interface to report allocated logical address to caller
      */
+    @ServiceThreadOnly
     void allocateLogicalAddress(final int deviceType, final int preferredAddress,
-            final AllocateLogicalAddressCallback callback) {
+            final AllocateAddressCallback callback) {
         assertRunOnServiceThread();
 
         runOnIoThread(new Runnable() {
@@ -177,8 +178,9 @@
         });
     }
 
+    @IoThreadOnly
     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
@@ -223,76 +225,9 @@
         return body;
     }
 
-    /**
-     * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
-     * logical address as new device info's.
-     *
-     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
-     *
-     * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
-     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
-     *         that has the same logical address as new one has.
-     */
-    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
-        assertRunOnServiceThread();
-        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
-        if (oldDeviceInfo != null) {
-            removeDeviceInfo(deviceInfo.getLogicalAddress());
-        }
-        mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
-        return oldDeviceInfo;
-    }
 
-    /**
-     * Remove a device info corresponding to the given {@code logicalAddress}.
-     * It returns removed {@link HdmiCecDeviceInfo} if exists.
-     *
-     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
-     *
-     * @param logicalAddress logical address of device to be removed
-     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
-     */
-    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
-        assertRunOnServiceThread();
-        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
-        if (deviceInfo != null) {
-            mDeviceInfos.remove(logicalAddress);
-        }
-        return deviceInfo;
-    }
-
-    /**
-     * Clear all device info.
-     *
-     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
-     */
-    void clearDeviceInfoList() {
-        assertRunOnServiceThread();
-        mDeviceInfos.clear();
-    }
-
-    /**
-     * Return a list of all {@link HdmiCecDeviceInfo}.
-     *
-     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
-     */
-    List<HdmiCecDeviceInfo> getDeviceInfoList() {
-        assertRunOnServiceThread();
-        return sparseArrayToList(mDeviceInfos);
-    }
-
-    /**
-     * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
-     *
-     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
-     *
-     * @param logicalAddress logical address to be retrieved
-     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
-     *         Returns null if no logical address matched
-     */
-    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
-        assertRunOnServiceThread();
-        return mDeviceInfos.get(logicalAddress);
+    HdmiPortInfo[] getPortInfos() {
+        return nativeGetPortInfos(mNativePtr);
     }
 
     /**
@@ -316,6 +251,7 @@
      * @param newLogicalAddress a logical address to be added
      * @return 0 on success. Otherwise, returns negative value
      */
+    @ServiceThreadOnly
     int addLogicalAddress(int newLogicalAddress) {
         assertRunOnServiceThread();
         if (HdmiCec.isValidAddress(newLogicalAddress)) {
@@ -330,6 +266,7 @@
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
+    @ServiceThreadOnly
     void clearLogicalAddress() {
         assertRunOnServiceThread();
         // TODO: consider to backup logical address so that new logical address
@@ -348,6 +285,7 @@
      * @return CEC physical address of the device. The range of success address
      *         is between 0x0000 and 0xFFFF. If failed it returns -1
      */
+    @ServiceThreadOnly
     int getPhysicalAddress() {
         assertRunOnServiceThread();
         return nativeGetPhysicalAddress(mNativePtr);
@@ -358,6 +296,7 @@
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
+    @ServiceThreadOnly
     int getVersion() {
         assertRunOnServiceThread();
         return nativeGetVersion(mNativePtr);
@@ -368,30 +307,66 @@
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
+    @ServiceThreadOnly
     int getVendorId() {
         assertRunOnServiceThread();
         return nativeGetVendorId(mNativePtr);
     }
 
     /**
+     * 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
+     */
+    @ServiceThreadOnly
+    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
+     */
+    @ServiceThreadOnly
+    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
+     */
+    @ServiceThreadOnly
+    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) {
+    @ServiceThreadOnly
+    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);
     }
 
@@ -400,20 +375,50 @@
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      */
+    @ServiceThreadOnly
     List<HdmiCecLocalDevice> getLocalDeviceList() {
         assertRunOnServiceThread();
-        return sparseArrayToList(mLocalDevices);
+        return HdmiUtils.sparseArrayToList(mLocalDevices);
     }
 
-    private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
-        ArrayList<T> list = new ArrayList<>();
-        for (int i = 0; i < array.size(); ++i) {
-            list.add(array.valueAt(i));
+    private List<Integer> pickPollCandidates(int pickStrategy) {
+        int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK;
+        Predicate<Integer> pickPredicate = null;
+        switch (strategy) {
+            case HdmiConstants.POLL_STRATEGY_SYSTEM_AUDIO:
+                pickPredicate = mSystemAudioAddressPredicate;
+                break;
+            case HdmiConstants.POLL_STRATEGY_REMOTES_DEVICES:
+            default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
+                pickPredicate = mRemoteDeviceAddressPredicate;
+                break;
         }
-        return list;
+
+        int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK;
+        ArrayList<Integer> pollingCandidates = new ArrayList<>();
+        switch (iterationStrategy) {
+            case HdmiConstants.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 HdmiConstants.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;
     }
 
+    @ServiceThreadOnly
     private boolean isAllocatedLocalDeviceAddress(int address) {
+        assertRunOnServiceThread();
         for (int i = 0; i < mLocalDevices.size(); ++i) {
             if (mLocalDevices.valueAt(i).isAddressOf(address)) {
                 return true;
@@ -422,6 +427,7 @@
         return false;
     }
 
+    @ServiceThreadOnly
     private void runDevicePolling(final List<Integer> candidates, final int retryCount,
             final DevicePollingCallback callback) {
         assertRunOnServiceThread();
@@ -446,6 +452,7 @@
         });
     }
 
+    @IoThreadOnly
     private boolean sendPollMessage(int address, int retryCount) {
         assertRunOnIoThread();
         for (int i = 0; i < retryCount; ++i) {
@@ -455,7 +462,7 @@
             // new logical address for the device because no device uses
             // it as logical address of the device.
             if (nativeSendCecCommand(mNativePtr, address, address, EMPTY_BODY)
-                    == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    == HdmiConstants.SEND_RESULT_SUCCESS) {
                 return true;
             }
         }
@@ -493,6 +500,7 @@
         return isAllocatedLocalDeviceAddress(address);
     }
 
+    @ServiceThreadOnly
     private void onReceiveCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
         if (isAcceptableAddress(message.getDestination())
@@ -505,24 +513,28 @@
             // Reply <Feature Abort> to initiator (source) for all requests.
             HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
                     sourceAddress, message.getSource(), message.getOpcode(),
-                    HdmiCecMessageBuilder.ABORT_REFUSED);
-            sendCommand(cecMessage, null);
+                    HdmiConstants.ABORT_REFUSED);
+            sendCommand(cecMessage);
         }
     }
 
+    @ServiceThreadOnly
     void sendCommand(HdmiCecMessage cecMessage) {
+        assertRunOnServiceThread();
         sendCommand(cecMessage, null);
     }
 
+    @ServiceThreadOnly
     void sendCommand(final HdmiCecMessage cecMessage,
             final HdmiControlService.SendMessageCallback callback) {
+        assertRunOnServiceThread();
         runOnIoThread(new Runnable() {
             @Override
             public void run() {
                 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
                 final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
                         cecMessage.getDestination(), body);
-                if (error != HdmiControlService.SEND_RESULT_SUCCESS) {
+                if (error != HdmiConstants.SEND_RESULT_SUCCESS) {
                     Slog.w(TAG, "Failed to send " + cecMessage);
                 }
                 if (callback != null) {
@@ -540,6 +552,7 @@
     /**
      * Called by native when incoming CEC message arrived.
      */
+    @ServiceThreadOnly
     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
         assertRunOnServiceThread();
         onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
@@ -562,4 +575,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 HdmiPortInfo[] nativeGetPortInfos(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/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
new file mode 100644
index 0000000..7080a56
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.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 HdmiCecKeycode {
+    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 HdmiCecKeycode() {
+    }
+
+    /**
+     * 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..bf7e57b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -16,116 +16,505 @@
 
 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.os.Looper;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * 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);
-    }
+    // Logical address of the active source.
+    @GuardedBy("mLock")
+    private int mActiveSource;
 
-    protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType,
-            AddressAllocationCallback callback) {
-        mController = controller;
+    // Active routing path. Physical address of the active source but not all the time, such as
+    // when the new active source does not claim itself to be one. Note that we don't keep
+    // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
+    @GuardedBy("mLock")
+    private int mActiveRoutingPath;
+
+    // Set to true while the service is in normal mode. While set to false, no input change is
+    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
+    // system upgrade, etc., a.k.a. "prohibit mode".
+    @GuardedBy("mLock")
+    private boolean mInputChangeEnabled;
+
+    protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
+    protected final Object mLock;
+
+    // A collection of FeatureAction.
+    // Note that access to this collection should happen in service thread.
+    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
+
+    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
+        mService = service;
         mDeviceType = deviceType;
-        mAllocationCallback = callback;
         mAddress = HdmiCec.ADDR_UNREGISTERED;
+        mLock = service.getServiceLock();
+
+        // TODO: Get control flag from persistent storage
+        mInputChangeEnabled = true;
     }
 
     // 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();
+    @ServiceThreadOnly
+    void init() {
+        assertRunOnServiceThread();
+        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;
+    /**
+     * Dispatch incoming message.
+     *
+     * @param message incoming message
+     * @return true if consumed a message; otherwise, return false.
+     */
+    @ServiceThreadOnly
+    final boolean dispatchMessage(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int dest = message.getDestination();
+        if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) {
+            return false;
+        }
+        // Cache incoming message. Note that it caches only white-listed one.
+        mCecMessageCache.cacheMessage(message);
+        return onMessage(message);
+    }
 
-                // Create and set device info.
-                HdmiCecDeviceInfo deviceInfo = createDeviceInfo(mAddress, deviceType);
-                setDeviceInfo(deviceInfo);
-                mController.addDeviceInfo(deviceInfo);
+    @ServiceThreadOnly
+    protected final boolean onMessage(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        if (dispatchMessageToAction(message)) {
+            return true;
+        }
+        switch (message.getOpcode()) {
+            case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+                return handleActiveSource(message);
+            case HdmiCec.MESSAGE_INACTIVE_SOURCE:
+                return handleInactiveSource(message);
+            case HdmiCec.MESSAGE_REQUEST_ACTIVE_SOURCE:
+                return handleRequestActiveSource(message);
+            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);
+            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+                return handleReportPhysicalAddress(message);
+            case HdmiCec.MESSAGE_INITIATE_ARC:
+                return handleInitiateArc(message);
+            case HdmiCec.MESSAGE_TERMINATE_ARC:
+                return handleTerminateArc(message);
+            case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
+                return handleSetSystemAudioMode(message);
+            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
+                return handleSystemAudioModeStatus(message);
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                return handleReportAudioStatus(message);
+            default:
+                return false;
+        }
+    }
 
-                mController.addLogicalAddress(logicalAddress);
-                onAddressAllocated(logicalAddress);
-                if (mAllocationCallback != null) {
-                    mAllocationCallback.onAddressAllocated(deviceType, logicalAddress);
-                }
+    @ServiceThreadOnly
+    private boolean dispatchMessageToAction(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        for (FeatureAction action : mActions) {
+            if (action.processCommand(message)) {
+                return true;
             }
-        });
+        }
+        return false;
     }
 
-    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);
+    @ServiceThreadOnly
+    protected boolean handleGivePhysicalAddress() {
+        assertRunOnServiceThread();
+
+        int physicalAddress = mService.getPhysicalAddress();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, physicalAddress, mDeviceType);
+        mService.sendCecCommand(cecMessage);
+        return true;
     }
 
+    @ServiceThreadOnly
+    protected boolean handleGiveDeviceVendorId() {
+        assertRunOnServiceThread();
+        int vendorId = mService.getVendorId();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+                mAddress, vendorId);
+        mService.sendCecCommand(cecMessage);
+        return true;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleGetCecVersion(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int version = mService.getCecVersion();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
+                message.getSource(), version);
+        mService.sendCecCommand(cecMessage);
+        return true;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleInactiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        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;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleGiveOsdName(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // 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;
+    }
+
+    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleTerminateArc(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleInitiateArc(HdmiCecMessage message) {
+        return false;
+    }
+
+    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    final void handleAddressAllocated(int logicalAddress) {
+        assertRunOnServiceThread();
+        mAddress = mPreferredAddress = logicalAddress;
+        onAddressAllocated(logicalAddress);
+    }
+
+    @ServiceThreadOnly
     HdmiCecDeviceInfo getDeviceInfo() {
+        assertRunOnServiceThread();
         return mDeviceInfo;
     }
 
+    @ServiceThreadOnly
     void setDeviceInfo(HdmiCecDeviceInfo info) {
+        assertRunOnServiceThread();
         mDeviceInfo = info;
     }
 
     // Returns true if the logical address is same as the argument.
+    @ServiceThreadOnly
     boolean isAddressOf(int addr) {
+        assertRunOnServiceThread();
         return addr == mAddress;
     }
 
     // Resets the logical address to unregistered(15), meaning the logical device is invalid.
+    @ServiceThreadOnly
     void clearAddress() {
+        assertRunOnServiceThread();
         mAddress = HdmiCec.ADDR_UNREGISTERED;
     }
 
+    @ServiceThreadOnly
     void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
         mPreferredAddress = addr;
     }
+
+    @ServiceThreadOnly
+    int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return mPreferredAddress;
+    }
+
+    @ServiceThreadOnly
+    void addAndStartAction(final FeatureAction action) {
+        assertRunOnServiceThread();
+        mActions.add(action);
+        action.start();
+    }
+
+    // See if we have an action of a given type in progress.
+    @ServiceThreadOnly
+    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
+        assertRunOnServiceThread();
+        for (FeatureAction action : mActions) {
+            if (action.getClass().equals(clazz)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Returns all actions matched with given class type.
+    @ServiceThreadOnly
+    <T extends FeatureAction> List<T> getActions(final Class<T> clazz) {
+        assertRunOnServiceThread();
+        ArrayList<T> actions = new ArrayList<>();
+        for (FeatureAction action : mActions) {
+            if (action.getClass().equals(clazz)) {
+                actions.add((T) action);
+            }
+        }
+        return actions;
+    }
+
+    /**
+     * Remove the given {@link FeatureAction} object from the action queue.
+     *
+     * @param action {@link FeatureAction} to remove
+     */
+    @ServiceThreadOnly
+    void removeAction(final FeatureAction action) {
+        assertRunOnServiceThread();
+        mActions.remove(action);
+    }
+
+    // Remove all actions matched with the given Class type.
+    @ServiceThreadOnly
+    <T extends FeatureAction> void removeAction(final Class<T> clazz) {
+        assertRunOnServiceThread();
+        removeActionExcept(clazz, null);
+    }
+
+    // Remove all actions matched with the given Class type besides |exception|.
+    @ServiceThreadOnly
+    <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);
+            }
+        }
+    }
+
+    protected void assertRunOnServiceThread() {
+        if (Looper.myLooper() != mService.getServiceLooper()) {
+            throw new IllegalStateException("Should run on service thread.");
+        }
+    }
+
+    /**
+     * Called when a hot-plug event issued.
+     *
+     * @param portId id of port where a hot-plug event happened
+     * @param connected whether to connected or not on the event
+     */
+    void onHotplug(int portId, boolean connected) {
+    }
+
+    final HdmiControlService getService() {
+        return mService;
+    }
+
+    @ServiceThreadOnly
+    final boolean isConnectedToArcPort(int path) {
+        assertRunOnServiceThread();
+        return mService.isConnectedToArcPort(path);
+    }
+
+    int getActiveSource() {
+        synchronized (mLock) {
+            return mActiveSource;
+        }
+    }
+
+    void setActiveSource(int source) {
+        synchronized (mLock) {
+            mActiveSource = source;
+        }
+    }
+
+    int getActivePath() {
+        synchronized (mLock) {
+            return mActiveRoutingPath;
+        }
+    }
+
+    void setActivePath(int path) {
+        synchronized (mLock) {
+            mActiveRoutingPath = path;
+        }
+    }
+
+    /**
+     * Returns the ID of the active HDMI port. The active port is the one that has the active
+     * routing path connected to it directly or indirectly under the device hierarchy.
+     */
+    int getActivePortId() {
+        synchronized (mLock) {
+            return mService.pathToPortId(mActiveRoutingPath);
+        }
+    }
+
+    /**
+     * Update the active port.
+     *
+     * @param portId the new active port id
+     */
+    void setActivePortId(int portId) {
+        synchronized (mLock) {
+            // We update active routing path instead, since we get the active port id from
+            // the active routing path.
+            mActiveRoutingPath = mService.portIdToPath(portId);
+        }
+    }
+
+    void updateActiveDevice(int logicalAddress, int physicalAddress) {
+        synchronized (mLock) {
+            mActiveSource = logicalAddress;
+            mActiveRoutingPath = physicalAddress;
+        }
+    }
+
+    void setInputChangeEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mInputChangeEnabled = enabled;
+        }
+    }
+
+    boolean isInPresetInstallationMode() {
+        // TODO: Change this to check the right flag.
+        synchronized (mLock) {
+            return !mInputChangeEnabled;
+        }
+    }
+
+    boolean isHdmiControlEnabled() {
+        synchronized (mLock) {
+            return !mInputChangeEnabled;
+        }
+    }
+
+    /**
+     * Whether the given path is located in the tail of current active path.
+     *
+     * @param path to be tested
+     * @return true if the given path is located in the tail of current active path; otherwise,
+     *         false
+     */
+    // TODO: move this to local device tv.
+    boolean isTailOfActivePath(int path) {
+        synchronized (mLock) {
+            // If active routing path is internal source, return false.
+            if (mActiveRoutingPath == 0) {
+                return false;
+            }
+            for (int i = 12; i >= 0; i -= 4) {
+                int curActivePath = (mActiveRoutingPath >> i) & 0xF;
+                if (curActivePath == 0) {
+                    return true;
+                } else {
+                    int curPath = (path >> i) & 0xF;
+                    if (curPath != curActivePath) {
+                        return false;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @ServiceThreadOnly
+    HdmiCecMessageCache getCecMessageCache() {
+        assertRunOnServiceThread();
+        return mCecMessageCache;
+    }
+
+    @ServiceThreadOnly
+    int pathToPortId(int newPath) {
+        assertRunOnServiceThread();
+        return mService.pathToPortId(newPath);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 3347725..d7e36b3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -17,24 +17,83 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
 /**
  * Represent a logical device of type Playback residing in Android system.
  */
 final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+    private static final String TAG = "HdmiCecLocalDevicePlayback";
 
-    HdmiCecLocalDevicePlayback(HdmiCecController controller, AddressAllocationCallback callback) {
-        super(controller, HdmiCec.DEVICE_PLAYBACK, callback);
+    HdmiCecLocalDevicePlayback(HdmiControlService service) {
+        super(service, HdmiCec.DEVICE_PLAYBACK);
     }
 
     @Override
-    void init() {
-        allocateAddress(mDeviceType);
-    }
-
-    @Override
+    @ServiceThreadOnly
     protected void onAddressAllocated(int logicalAddress) {
-        mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                mAddress, mController.getPhysicalAddress(), mDeviceType));
+        assertRunOnServiceThread();
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mService.getPhysicalAddress(), mDeviceType));
+    }
+
+    @ServiceThreadOnly
+    void oneTouchPlay(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        if (hasAction(OneTouchPlayAction.class)) {
+            Slog.w(TAG, "oneTouchPlay already in progress");
+            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+            return;
+        }
+
+        // TODO: Consider the case of multiple TV sets. For now we always direct the command
+        //       to the primary one.
+        OneTouchPlayAction action = OneTouchPlayAction.create(this, HdmiCec.ADDR_TV, callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate oneTouchPlay");
+            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
+    }
+
+    @ServiceThreadOnly
+    void queryDisplayStatus(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        if (hasAction(DevicePowerStatusAction.class)) {
+            Slog.w(TAG, "queryDisplayStatus already in progress");
+            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+            return;
+        }
+        DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
+                HdmiCec.ADDR_TV, callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate queryDisplayStatus");
+            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
+    }
+
+    @ServiceThreadOnly
+    private void invokeCallback(IHdmiControlCallback callback, int result) {
+        assertRunOnServiceThread();
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
+
+    @Override
+    @ServiceThreadOnly
+    void onHotplug(int portId, boolean connected) {
+        assertRunOnServiceThread();
+        // TODO: clear devices connected to the given port id.
+        mCecMessageCache.flushAll();
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 93761ab..718072a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -17,30 +17,814 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.media.AudioSystem;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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);
+    // Whether ARC is "enabled" or not.
+    @GuardedBy("mLock")
+    private boolean mArcStatusEnabled = false;
+
+    // Whether SystemAudioMode is "On" or not.
+    @GuardedBy("mLock")
+    private boolean mSystemAudioMode;
+
+    // The previous port id (input) before switching to the new one. This is remembered in order to
+    // be able to switch to it upon receiving <Inactive Source> from currently active source.
+    // This remains valid only when the active source was switched via one touch play operation
+    // (either by TV or source device). Manual port switching invalidates this value to
+    // HdmiConstants.PORT_INVALID, for which case <Inactive Source> does not do anything.
+    @GuardedBy("mLock")
+    private int mPrevPortId;
+
+    @GuardedBy("mLock")
+    private int mSystemAudioVolume = HdmiConstants.UNKNOWN_VOLUME;
+
+    @GuardedBy("mLock")
+    private boolean mSystemAudioMute = false;
+
+    // Copy of mDeviceInfos to guarantee thread-safety.
+    @GuardedBy("mLock")
+    private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
+    // All external cec device which excludes local devices.
+    @GuardedBy("mLock")
+    private List<HdmiCecDeviceInfo> mSafeExternalDeviceInfos = Collections.emptyList();
+
+    // Map-like container of all cec devices including local ones.
+    // A logical address of device is used as key of container.
+    // This is not thread-safe. For external purpose use mSafeDeviceInfos.
+    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
+
+    HdmiCecLocalDeviceTv(HdmiControlService service) {
+        super(service, HdmiCec.DEVICE_TV);
+        mPrevPortId = HdmiConstants.INVALID_PORT_ID;
+        // TODO: load system audio mode and set it to mSystemAudioMode.
     }
 
     @Override
-    void init() {
-        allocateAddress(mDeviceType);
-    }
-
-    @Override
+    @ServiceThreadOnly
     protected void onAddressAllocated(int logicalAddress) {
+        assertRunOnServiceThread();
         // 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()));
 
-        // TODO: Start routing control action, device discovery action.
+        launchDeviceDiscovery();
+        // TODO: Start routing control action
+    }
+
+    /**
+     * Performs the action 'device select', or 'one touch play' initiated by TV.
+     *
+     * @param targetAddress logical address of the device to select
+     * @param callback callback object to report the result with
+     */
+    @ServiceThreadOnly
+    void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        if (targetAddress == HdmiCec.ADDR_INTERNAL) {
+            handleSelectInternalSource(callback);
+            return;
+        }
+        HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
+        if (targetDevice == null) {
+            invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE);
+            return;
+        }
+        removeAction(DeviceSelectAction.class);
+        addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
+    }
+
+    @ServiceThreadOnly
+    private void handleSelectInternalSource(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        // Seq #18
+        if (isHdmiControlEnabled() && getActiveSource() != mAddress) {
+            updateActiveSource(mAddress, mService.getPhysicalAddress());
+            // TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
+            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                    mAddress, mService.getPhysicalAddress());
+            mService.sendCecCommand(activeSource);
+        }
+    }
+
+    @ServiceThreadOnly
+    void updateActiveSource(int activeSource, int activePath) {
+        assertRunOnServiceThread();
+        // Seq #14
+        if (activeSource == getActiveSource() && activePath == getActivePath()) {
+            return;
+        }
+        setActiveSource(activeSource);
+        setActivePath(activePath);
+        if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
+            if (mService.pathToPortId(activePath) == getActivePortId()) {
+                setPrevPortId(getActivePortId());
+            }
+            // TODO: Show the OSD banner related to the new active source device.
+        } else {
+            // TODO: If displayed, remove the OSD banner related to the previous
+            //       active source device.
+        }
+    }
+
+    /**
+     * Returns the previous port id kept to handle input switching on <Inactive Source>.
+     */
+    int getPrevPortId() {
+        synchronized (mLock) {
+            return mPrevPortId;
+        }
+    }
+
+    /**
+     * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
+     * taken for <Inactive Source>.
+     */
+    void setPrevPortId(int portId) {
+        synchronized (mLock) {
+            mPrevPortId = portId;
+        }
+    }
+
+    @ServiceThreadOnly
+    void updateActivePortId(int portId) {
+        assertRunOnServiceThread();
+        // Seq #15
+        if (portId == getActivePortId()) {
+            return;
+        }
+        setPrevPortId(portId);
+        // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
+        //       Show OSD port change banner
+    }
+
+    @ServiceThreadOnly
+    void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        // Seq #20
+        if (!isHdmiControlEnabled() || portId == getActivePortId()) {
+            invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE);
+            return;
+        }
+        // TODO: Make sure this call does not stem from <Active Source> message reception.
+
+        setActivePortId(portId);
+        // TODO: Return immediately if the operation is triggered by <Text/Image View On>
+        //       and this is the first notification about the active input after power-on.
+        // TODO: Handle invalid port id / active input which should be treated as an
+        //       internal tuner.
+
+        removeAction(RoutingControlAction.class);
+
+        int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
+        int newPath = mService.portIdToPath(portId);
+        HdmiCecMessage routingChange =
+                HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
+        mService.sendCecCommand(routingChange);
+        addAndStartAction(new RoutingControlAction(this, newPath, callback));
+    }
+
+    /**
+     * Sends key to a target CEC device.
+     *
+     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
+     * @param isPressed true if this is keypress event
+     */
+    @ServiceThreadOnly
+    void sendKeyEvent(int keyCode, boolean isPressed) {
+        assertRunOnServiceThread();
+        List<SendKeyAction> action = getActions(SendKeyAction.class);
+        if (!action.isEmpty()) {
+            action.get(0).processKeyEvent(keyCode, isPressed);
+        } else {
+            if (isPressed) {
+                addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode));
+            } else {
+                Slog.w(TAG, "Discard key release event");
+            }
+        }
+    }
+
+    private static void invokeCallback(IHdmiControlCallback callback, int result) {
+        if (callback == null) {
+            return;
+        }
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int activePath = HdmiUtils.twoBytesToInt(message.getParams());
+        ActiveSourceHandler.create(this, null).process(message.getSource(), activePath);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleInactiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // Seq #10
+
+        // Ignore <Inactive Source> from non-active source device.
+        if (getActiveSource() != message.getSource()) {
+            return true;
+        }
+        if (isInPresetInstallationMode()) {
+            return true;
+        }
+        int portId = getPrevPortId();
+        if (portId != HdmiConstants.INVALID_PORT_ID) {
+            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
+
+            HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource());
+            if (inactiveSource == null) {
+                return true;
+            }
+            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
+                return true;
+            }
+            // TODO: Switch the TV freeze mode off
+
+            setActivePortId(portId);
+            doManualPortSwitching(portId, null);
+            setPrevPortId(HdmiConstants.INVALID_PORT_ID);
+        }
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // Seq #19
+        int address = getDeviceInfo().getLogicalAddress();
+        if (address == getActiveSource()) {
+            mService.sendCecCommand(
+                    HdmiCecMessageBuilder.buildActiveSource(address, getActivePath()));
+        }
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        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;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // Ignore if [Device Discovery Action] is going on.
+        if (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());
+        int logicalAddress = message.getSource();
+
+        // If it is a new device and connected to the tail of active path,
+        // it's required to change routing path.
+        boolean requireRoutingChange = !isInDeviceList(physicalAddress, logicalAddress)
+                && isTailOfActivePath(physicalAddress);
+        addAndStartAction(new NewDeviceAction(this, message.getSource(), physicalAddress,
+                requireRoutingChange));
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleVendorSpecificCommand(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        List<VendorSpecificAction> actions = Collections.emptyList();
+        // TODO: Call mService.getActions(VendorSpecificAction.class) to get all the actions.
+
+        // We assume that there can be multiple vendor-specific command actions running
+        // at the same time. Pass the message to each action to see if one of them needs it.
+        for (VendorSpecificAction action : actions) {
+            if (action.processCommand(message)) {
+                return true;
+            }
+        }
+        // Handle the message here if it is not already consumed by one of the running actions.
+        // Respond with a appropriate vendor-specific command or <Feature Abort>, or create another
+        // vendor-specific action:
+        //
+        // mService.addAndStartAction(new VendorSpecificAction(mService, mAddress));
+        //
+        // For now, simply reply with <Feature Abort> and mark it consumed by returning true.
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                message.getDestination(), message.getSource(), message.getOpcode(),
+                HdmiConstants.ABORT_REFUSED));
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        byte params[] = message.getParams();
+        if (params.length < 1) {
+            Slog.w(TAG, "Invalide <Report Audio Status> message:" + message);
+            return true;
+        }
+        int mute = params[0] & 0x80;
+        int volume = params[0] & 0x7F;
+        setAudioStatus(mute == 0x80, volume);
+        return true;
+    }
+
+    @ServiceThreadOnly
+    private void launchDeviceDiscovery() {
+        assertRunOnServiceThread();
+        clearDeviceInfoList();
+        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
+                new DeviceDiscoveryCallback() {
+                    @Override
+                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+                        for (HdmiCecDeviceInfo info : deviceInfos) {
+                            addCecDevice(info);
+                        }
+
+                        // Since we removed all devices when it's start and
+                        // device discovery action does not poll local devices,
+                        // we should put device info of local device manually here
+                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                            addCecDevice(device.getDeviceInfo());
+                        }
+
+                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
+
+                        // If there is AVR, initiate System Audio Auto initiation action,
+                        // which turns on and off system audio according to last system
+                        // audio setting.
+                        HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo();
+                        if (avrInfo != null) {
+                            addAndStartAction(new SystemAudioAutoInitiationAction(
+                                    HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress()));
+                        }
+                    }
+                });
+        addAndStartAction(action);
+    }
+
+    // Clear all device info.
+    @ServiceThreadOnly
+    private void clearDeviceInfoList() {
+        assertRunOnServiceThread();
+        mDeviceInfos.clear();
+        updateSafeDeviceInfoList();
+    }
+
+    @ServiceThreadOnly
+    void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        if (avr == null) {
+            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+            return;
+        }
+
+        addAndStartAction(
+                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
+    }
+
+    void setSystemAudioMode(boolean on) {
+        synchronized (mLock) {
+            if (on != mSystemAudioMode) {
+                mSystemAudioMode = on;
+                // TODO: Need to set the preference for SystemAudioMode.
+                mService.announceSystemAudioModeChange(on);
+            }
+        }
+    }
+
+    boolean getSystemAudioMode() {
+        synchronized (mLock) {
+            return mSystemAudioMode;
+        }
+    }
+
+    /**
+     * Change ARC status into the given {@code enabled} status.
+     *
+     * @return {@code true} if ARC was in "Enabled" status
+     */
+    boolean setArcStatus(boolean enabled) {
+        synchronized (mLock) {
+            boolean oldStatus = mArcStatusEnabled;
+            // 1. Enable/disable ARC circuit.
+            mService.setAudioReturnChannel(enabled);
+            // 2. Notify arc status to audio service.
+            notifyArcStatusToAudioService(enabled);
+            // 3. Update arc status;
+            mArcStatusEnabled = enabled;
+            return oldStatus;
+        }
+    }
+
+    private void notifyArcStatusToAudioService(boolean enabled) {
+        // Note that we don't set any name to ARC.
+        mService.getAudioManager().setWiredDeviceConnectionState(
+                AudioSystem.DEVICE_OUT_HDMI_ARC,
+                enabled ? 1 : 0, "");
+    }
+
+    /**
+     * Returns whether ARC is enabled or not.
+     */
+    boolean getArcStatus() {
+        synchronized (mLock) {
+            return mArcStatusEnabled;
+        }
+    }
+
+    void setAudioStatus(boolean mute, int volume) {
+        synchronized (mLock) {
+            mSystemAudioMute = mute;
+            mSystemAudioVolume = volume;
+            // TODO: pass volume to service (audio service) after scale it to local volume level.
+            mService.setAudioStatus(mute, volume);
+        }
+    }
+
+    @ServiceThreadOnly
+    void changeVolume(int curVolume, int delta, int maxVolume) {
+        assertRunOnServiceThread();
+        if (delta == 0 || !isSystemAudioOn()) {
+            return;
+        }
+
+        int targetVolume = curVolume + delta;
+        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
+        synchronized (mLock) {
+            // If new volume is the same as current system audio volume, just ignore it.
+            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
+            if (cecVolume == mSystemAudioVolume) {
+                // Update tv volume with system volume value.
+                mService.setAudioStatus(false,
+                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
+                return;
+            }
+        }
+
+        // Remove existing volume action.
+        removeAction(VolumeControlAction.class);
+
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
+                cecVolume, delta > 0));
+    }
+
+    @ServiceThreadOnly
+    void changeMute(boolean mute) {
+        assertRunOnServiceThread();
+        if (!isSystemAudioOn()) {
+            return;
+        }
+
+        // Remove existing volume action.
+        removeAction(VolumeControlAction.class);
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
+    }
+
+    private boolean isSystemAudioOn() {
+        if (getAvrDeviceInfo() == null) {
+            return false;
+        }
+
+        synchronized (mLock) {
+            return mSystemAudioMode;
+        }
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleInitiateArc(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // In case where <Initiate Arc> is started by <Request ARC Initiation>
+        // need to clean up RequestArcInitiationAction.
+        removeAction(RequestArcInitiationAction.class);
+        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
+                message.getSource(), true);
+        addAndStartAction(action);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleTerminateArc(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // In case where <Terminate Arc> is started by <Request ARC Termination>
+        // need to clean up RequestArcInitiationAction.
+        // TODO: check conditions of power status by calling is_connected api
+        // to be added soon.
+        removeAction(RequestArcTerminationAction.class);
+        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
+                message.getSource(), false);
+        addAndStartAction(action);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        if (!isMessageForSystemAudio(message)) {
+            return false;
+        }
+        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
+                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
+        addAndStartAction(action);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        if (!isMessageForSystemAudio(message)) {
+            return false;
+        }
+        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
+        return true;
+    }
+
+    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;
+    }
+
+    /**
+     * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
+     * logical address as new device info's.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     *
+     * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
+     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
+     *         that has the same logical address as new one has.
+     */
+    @ServiceThreadOnly
+    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
+        if (oldDeviceInfo != null) {
+            removeDeviceInfo(deviceInfo.getLogicalAddress());
+        }
+        mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
+        updateSafeDeviceInfoList();
+        return oldDeviceInfo;
+    }
+
+    /**
+     * Remove a device info corresponding to the given {@code logicalAddress}.
+     * It returns removed {@link HdmiCecDeviceInfo} if exists.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     *
+     * @param logicalAddress logical address of device to be removed
+     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
+     */
+    @ServiceThreadOnly
+    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
+        if (deviceInfo != null) {
+            mDeviceInfos.remove(logicalAddress);
+        }
+        updateSafeDeviceInfoList();
+        return deviceInfo;
+    }
+
+    /**
+     * Return a list of all {@link HdmiCecDeviceInfo}.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}.
+     */
+    @ServiceThreadOnly
+    List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) {
+        assertRunOnServiceThread();
+        if (includelLocalDevice) {
+            return HdmiUtils.sparseArrayToList(mDeviceInfos);
+        } else {
+            ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
+            for (int i = 0; i < mDeviceInfos.size(); ++i) {
+                HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
+                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
+                    infoList.add(info);
+                }
+            }
+            return infoList;
+        }
+    }
+
+    /**
+     * Return a list of  {@link HdmiCecDeviceInfo}.
+     *
+     * @param includeLocalDevice whether to include local device in result.
+     */
+    List<HdmiCecDeviceInfo> getSafeDeviceInfoList(boolean includeLocalDevice) {
+        synchronized (mLock) {
+            if (includeLocalDevice) {
+                return mSafeAllDeviceInfos;
+            } else {
+                return mSafeExternalDeviceInfos;
+            }
+        }
+    }
+
+    @ServiceThreadOnly
+    private void updateSafeDeviceInfoList() {
+        assertRunOnServiceThread();
+        List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
+        List<HdmiCecDeviceInfo> externalDeviceInfos = getDeviceInfoList(false);
+        synchronized (mLock) {
+            mSafeAllDeviceInfos = copiedDevices;
+            mSafeExternalDeviceInfos = externalDeviceInfos;
+        }
+    }
+
+    @ServiceThreadOnly
+    private boolean isLocalDeviceAddress(int address) {
+        assertRunOnServiceThread();
+        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+            if (device.isAddressOf(address)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @ServiceThreadOnly
+    HdmiCecDeviceInfo getAvrDeviceInfo() {
+        assertRunOnServiceThread();
+        return getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
+    }
+
+    /**
+     * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
+     *
+     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}.
+     *
+     * @param logicalAddress logical address to be retrieved
+     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
+     *         Returns null if no logical address matched
+     */
+    @ServiceThreadOnly
+    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
+        return mDeviceInfos.get(logicalAddress);
+    }
+
+    boolean hasSystemAudioDevice() {
+        return getSafeAvrDeviceInfo() != null;
+    }
+
+    HdmiCecDeviceInfo getSafeAvrDeviceInfo() {
+        return getSafeDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
+    }
+
+    /**
+     * Thread safe version of {@link #getDeviceInfo(int)}.
+     *
+     * @param logicalAddress logical address to be retrieved
+     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
+     *         Returns null if no logical address matched
+     */
+    HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) {
+        synchronized (mLock) {
+            return mSafeAllDeviceInfos.get(logicalAddress);
+        }
+    }
+
+    /**
+     * Called when a device is newly added or a new device is detected.
+     *
+     * @param info device info of a new device.
+     */
+    @ServiceThreadOnly
+    final void addCecDevice(HdmiCecDeviceInfo info) {
+        assertRunOnServiceThread();
+        addDeviceInfo(info);
+        if (info.getLogicalAddress() == mAddress) {
+            // The addition of TV device itself should not be notified.
+            return;
+        }
+        mService.invokeDeviceEventListeners(info, true);
+    }
+
+    /**
+     * Called when a device is removed or removal of device is detected.
+     *
+     * @param address a logical address of a device to be removed
+     */
+    @ServiceThreadOnly
+    final void removeCecDevice(int address) {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo info = removeDeviceInfo(address);
+        mCecMessageCache.flushMessagesFrom(address);
+        mService.invokeDeviceEventListeners(info, false);
+    }
+
+    /**
+     * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches
+     * the given routing path. CEC devices use routing path for its physical address to
+     * describe the hierarchy of the devices in the network.
+     *
+     * @param path routing path or physical address
+     * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null
+     */
+    @ServiceThreadOnly
+    final HdmiCecDeviceInfo getDeviceInfoByPath(int path) {
+        assertRunOnServiceThread();
+        for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) {
+            if (info.getPhysicalAddress() == path) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Whether a device of the specified physical address and logical address exists
+     * in a device info list. However, both are minimal condition and it could
+     * be different device from the original one.
+     *
+     * @param physicalAddress physical address of a device to be searched
+     * @param logicalAddress logical address of a device to be searched
+     * @return true if exist; otherwise false
+     */
+    @ServiceThreadOnly
+    boolean isInDeviceList(int physicalAddress, int logicalAddress) {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress);
+        if (device == null) {
+            return false;
+        }
+        return device.getPhysicalAddress() == physicalAddress;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    void onHotplug(int portNo, boolean connected) {
+        assertRunOnServiceThread();
+
+        // Tv device will have permanent HotplugDetectionAction.
+        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
+        if (!hotplugActions.isEmpty()) {
+            // Note that hotplug action is single action running on a machine.
+            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
+            hotplugActions.get(0).pollAllDevicesNow();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 9a76734..361a063 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() {}
@@ -279,6 +270,38 @@
     }
 
     /**
+     * Build &lt;Set Stream Path&gt; command.
+     *
+     * <p>This is a broadcast message sent to all devices on the bus.
+     *
+     * @param src source address of command
+     * @param streamPath physical address of the device to start streaming
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetStreamPath(int src, int streamPath) {
+        return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_SET_STREAM_PATH,
+                physicalAddressToParam(streamPath));
+    }
+
+    /**
+     * Build &lt;Routing Change&gt; command.
+     *
+     * <p>This is a broadcast message sent to all devices on the bus.
+     *
+     * @param src source address of command
+     * @param oldPath physical address of the currently active routing path
+     * @param newPath physical address of the new active routing path
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildRoutingChange(int src, int oldPath, int newPath) {
+        byte[] param = new byte[] {
+            (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF),
+            (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF)
+        };
+        return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_ROUTING_CHANGE, param);
+    }
+
+    /**
      * Build &lt;Give Device Power Status&gt; command.
      *
      * @param src source address of command
@@ -290,6 +313,84 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
+     * Build &lt;Give System Audio Mode Status&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
+    }
+
+    /***** 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..ab5b8d8
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -0,0 +1,108 @@
+/*
+ * 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_POWER = 0x40;
+    static final int UI_COMMAND_VOLUME_UP = 0x41;
+    static final int UI_COMMAND_VOLUME_DOWN = 0x42;
+    static final int UI_COMMAND_MUTE = 0x43;
+    static final int UI_COMMAND_MUTE_FUNCTION = 0x65;
+    static final int UI_COMMAND_RESTORE_VOLUME_FUNCTION = 0x66;
+    static final int UI_COMMAND_POWER_ON_FUNCTION = 0x6D;
+
+    // Bit mask used to get the routing path of the top level device.
+    // When &'d with the path 1.2.2.0 (0x1220), for instance, gives 1.0.0.0.
+    static final int ROUTING_PATH_TOP_MASK = 0xF000;
+
+    // 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;
+
+    static final int INVALID_PORT_ID = -1;
+    static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+
+    // Send result codes.
+    static final int SEND_RESULT_SUCCESS = 0;
+    static final int SEND_RESULT_NAK = -1;
+    static final int SEND_RESULT_FAILURE = -2;
+
+    // Strategy for device polling.
+    // Should use "OR(|) operation of POLL_STRATEGY_XXX and POLL_ITERATION_XXX.
+    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;
+
+    static final int UNKNOWN_VOLUME = -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.
+    static final int IRT_MS = 300;
+
+    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..ad95181 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -21,27 +21,32 @@
 import android.hardware.hdmi.HdmiCec;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.HdmiHotplugEvent;
+import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
+import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
+import android.media.AudioManager;
+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.HdmiAnnotations.ServiceThreadOnly;
+import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 
 import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 
 /**
  * Provides a service for sending and processing HDMI control messages,
@@ -53,10 +58,6 @@
     // TODO: Rename the permission to HDMI_CONTROL.
     private static final String PERMISSION = "android.permission.HDMI_CEC";
 
-    static final int SEND_RESULT_SUCCESS = 0;
-    static final int SEND_RESULT_NAK = -1;
-    static final int SEND_RESULT_FAILURE = -2;
-
     /**
      * Interface to report send result.
      */
@@ -89,65 +90,66 @@
     // and sparse call it shares a thread to handle IO operations.
     private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
 
-    // A collection of FeatureAction.
-    // Note that access to this collection should happen in service thread.
-    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
-
     // Used to synchronize the access to the service.
     private final Object mLock = new Object();
 
-    // Type of logical devices hosted in the system.
-    @GuardedBy("mLock")
-    private final int[] mLocalDevices;
+    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
+    private final List<Integer> mLocalDevices;
 
     // List of listeners registered by callers that want to get notified of
     // hotplug events.
+    @GuardedBy("mLock")
     private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
 
     // List of records for hotplug event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
             new ArrayList<>();
 
+    // List of listeners registered by callers that want to get notified of
+    // device status events.
+    @GuardedBy("mLock")
+    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
+
+    // List of records for device event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
+    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
+            new ArrayList<>();
+
+    // List of listeners registered by callers that want to get notified of
+    // system audio mode changes.
+    private final ArrayList<IHdmiSystemAudioModeChangeListener>
+            mSystemAudioModeChangeListeners = new ArrayList<>();
+    // List of records for system audio mode change to handle the the caller killed in action.
+    private final ArrayList<SystemAudioModeChangeListenerRecord>
+            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
+
+    // Handler used to run a task in service thread.
+    private final Handler mHandler = new Handler();
+
     @Nullable
     private HdmiCecController mCecController;
 
     @Nullable
     private HdmiMhlController mMhlController;
 
-    // Whether ARC is "enabled" or not.
-    // TODO: it may need to hold lock if it's accessed from others.
-    private boolean mArcStatusEnabled = false;
-
-    // Handler running on service thread. It's used to run a task in service thread.
-    private Handler mHandler = new Handler();
+    // HDMI port information. Stored in the unmodifiable list to keep the static information
+    // from being modified.
+    private List<HdmiPortInfo> mPortInfo;
 
     public HdmiControlService(Context context) {
         super(context);
-        mLocalDevices = getContext().getResources().getIntArray(
-                com.android.internal.R.array.config_hdmiCecLogicalDeviceType);
+        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
+                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
     }
 
     @Override
     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.");
         }
@@ -156,8 +158,146 @@
         if (mMhlController == null) {
             Slog.i(TAG, "Device does not support MHL-control.");
         }
-
+        mPortInfo = initPortInfo();
         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.
+    }
+
+    @ServiceThreadOnly
+    private void initializeLocalDevices(final List<Integer> deviceTypes) {
+        assertRunOnServiceThread();
+        // A container for [Logical Address, Local device info].
+        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
+        final SparseIntArray finished = new SparseIntArray();
+        mCecController.clearLogicalAddress();
+        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);
+
+                    // Address allocation completed for all devices. Notify each device.
+                    if (deviceTypes.size() == finished.size()) {
+                        notifyAddressAllocated(devices);
+                    }
+                }
+            });
+        }
+    }
+
+    @ServiceThreadOnly
+    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
+        assertRunOnServiceThread();
+        for (int i = 0; i < devices.size(); ++i) {
+            int address = devices.keyAt(i);
+            HdmiCecLocalDevice device = devices.valueAt(i);
+            device.handleAddressAllocated(address);
+        }
+    }
+
+    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
+    // keep them in one place.
+    @ServiceThreadOnly
+    private List<HdmiPortInfo> initPortInfo() {
+        assertRunOnServiceThread();
+        HdmiPortInfo[] cecPortInfo = null;
+
+        // CEC HAL provides majority of the info while MHL does only MHL support flag for
+        // each port. Return empty array if CEC HAL didn't provide the info.
+        if (mCecController != null) {
+            cecPortInfo = mCecController.getPortInfos();
+        }
+        if (cecPortInfo == null) {
+            return Collections.emptyList();
+        }
+
+        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
+        if (mMhlController != null) {
+            // TODO: Implement plumbing logic to get MHL port information.
+            // mhlPortInfo = mMhlController.getPortInfos();
+        }
+
+        // Use the id (port number) to find the matched info between CEC and MHL to combine them
+        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
+        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
+        for (int i = 0; i < cecPortInfo.length; ++i) {
+            HdmiPortInfo cec = cecPortInfo[i];
+            int id = cec.getId();
+            boolean mhlInfoFound = false;
+            for (HdmiPortInfo mhl : mhlPortInfo) {
+                if (id == mhl.getId()) {
+                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
+                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
+                    mhlInfoFound = true;
+                    break;
+                }
+            }
+            if (!mhlInfoFound) {
+                result.add(cec);
+            }
+        }
+
+        return Collections.unmodifiableList(result);
+    }
+
+    /**
+     * Returns HDMI port information for the given port id.
+     *
+     * @param portId HDMI port id
+     * @return {@link HdmiPortInfo} for the given port
+     */
+    HdmiPortInfo getPortInfo(int portId) {
+        // mPortInfo is an unmodifiable list and the only reference to its inner list.
+        // No lock is necessary.
+        for (HdmiPortInfo info : mPortInfo) {
+            if (portId == info.getId()) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the routing path (physical address) of the HDMI port for the given
+     * port id.
+     */
+    int portIdToPath(int portId) {
+        HdmiPortInfo portInfo = getPortInfo(portId);
+        if (portInfo == null) {
+            Slog.e(TAG, "Cannot find the port info: " + portId);
+            return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
+        }
+        return portInfo.getAddress();
+    }
+
+    /**
+     * Returns the id of HDMI port located at the top of the hierarchy of
+     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
+     * the port id to be returned is the ID associated with the port address
+     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
+     */
+    int pathToPortId(int path) {
+        int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK;
+        for (HdmiPortInfo info : mPortInfo) {
+            if (portAddress == info.getAddress()) {
+                return info.getId();
+            }
+        }
+        return HdmiConstants.INVALID_PORT_ID;
     }
 
     /**
@@ -180,79 +320,61 @@
     }
 
     /**
-     * Add and start a new {@link FeatureAction} to the action queue.
-     *
-     * @param action {@link FeatureAction} to add and start
+     * Returns physical address of the device.
      */
-    void addAndStartAction(final FeatureAction action) {
-        // TODO: may need to check the number of stale actions.
-        runOnServiceThread(new Runnable() {
-            @Override
-            public void run() {
-                mActions.add(action);
-                action.start();
-            }
-        });
+    int getPhysicalAddress() {
+        return mCecController.getPhysicalAddress();
     }
 
-    // See if we have an action of a given type in progress.
-    private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
-        for (FeatureAction action : mActions) {
-            if (action.getClass().equals(clazz)) {
+    /**
+     * Returns vendor id of CEC service.
+     */
+    int getVendorId() {
+        return mCecController.getVendorId();
+    }
+
+    @ServiceThreadOnly
+    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
+        HdmiCecLocalDeviceTv tv = tv();
+        if (tv == null) {
+            return null;
+        }
+        return tv.getDeviceInfo(logicalAddress);
+    }
+
+    /**
+     * Returns version of CEC.
+     */
+    int getCecVersion() {
+        return mCecController.getVersion();
+    }
+
+    /**
+     * Whether a device of the specified physical address is connected to ARC enabled port.
+     */
+    boolean isConnectedToArcPort(int physicalAddress) {
+        for (HdmiPortInfo portInfo : mPortInfo) {
+            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
+                    && portInfo.isArcSupported()) {
                 return true;
             }
         }
         return false;
     }
 
-    /**
-     * Remove the given {@link FeatureAction} object from the action queue.
-     *
-     * @param action {@link FeatureAction} to remove
-     */
-    void removeAction(final FeatureAction action) {
-        runOnServiceThread(new Runnable() {
-            @Override
-            public void run() {
-                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);
-                    }
-                }
-            }
-        });
-    }
-
-    private void runOnServiceThread(Runnable runnable) {
+    void runOnServiceThread(Runnable runnable) {
         mHandler.post(runnable);
     }
 
-    /**
-     * 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.
+    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
+        mHandler.postAtFrontOfQueue(runnable);
+    }
 
-        // 2. Update arc status;
-        mArcStatusEnabled = enabled;
-        return oldStatus;
+    private void assertRunOnServiceThread() {
+        if (Looper.myLooper() != mHandler.getLooper()) {
+            throw new IllegalStateException("Should run on service thread.");
+        }
     }
 
     /**
@@ -261,56 +383,40 @@
      * @param command CEC command to send out
      * @param callback interface used to the result of send command
      */
+    @ServiceThreadOnly
     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+        assertRunOnServiceThread();
         mCecController.sendCommand(command, callback);
     }
 
+    @ServiceThreadOnly
     void sendCecCommand(HdmiCecMessage command) {
+        assertRunOnServiceThread();
         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.
+    @ServiceThreadOnly
+    boolean handleCecCommand(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        return dispatchMessageToLocalDevice(message);
     }
 
-    boolean handleCecCommand(HdmiCecMessage message) {
-        // Commands that queries system information replies directly instead
-        // of creating FeatureAction because they are state-less.
-        switch (message.getOpcode()) {
-            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
-                handleGetMenuLanguage(message);
+    void setAudioReturnChannel(boolean enabled) {
+        mCecController.setAudioReturnChannel(enabled);
+    }
+
+    @ServiceThreadOnly
+    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+            if (device.dispatchMessage(message)
+                    && message.getDestination() != HdmiCec.ADDR_BROADCAST) {
                 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);
-                return true;
-            // TODO: Add remaining system information query such as
-            // <Give Device Power Status> and <Request Active Source> handler.
-            default:
-                return dispatchMessageToAction(message);
+            }
         }
+
+        Slog.w(TAG, "Unhandled cec command:" + message);
+        return false;
     }
 
     /**
@@ -319,8 +425,13 @@
      * @param portNo hdmi port number where hot plug event issued.
      * @param connected whether to be plugged in or not
      */
+    @ServiceThreadOnly
     void onHotplug(int portNo, boolean connected) {
-        // TODO: Start "RequestArcInitiationAction" if ARC port.
+        assertRunOnServiceThread();
+        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+            device.onHotplug(portNo, connected);
+        }
+        announceHotplugEvent(portNo, connected);
     }
 
     /**
@@ -328,114 +439,52 @@
      * 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);
+    @ServiceThreadOnly
+    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
+        assertRunOnServiceThread();
+        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 & HdmiConstants.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 & HdmiConstants.POLL_ITERATION_STRATEGY_MASK;
+        if (iterationStrategy == 0) {
+            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
         }
-
-        // TODO: start new device action.
+        return strategy | iterationStrategy;
     }
 
-    private void handleInitiateArc(HdmiCecMessage message){
-        // In case where <Initiate Arc> is started by <Request ARC Initiation>
-        // need to clean up RequestArcInitiationAction.
-        removeAction(RequestArcInitiationAction.class);
-        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
-                message.getDestination(), message.getSource(), true);
-        addAndStartAction(action);
+    List<HdmiCecLocalDevice> getAllLocalDevices() {
+        assertRunOnServiceThread();
+        return mCecController.getLocalDeviceList();
     }
 
-    private void handleTerminateArc(HdmiCecMessage message) {
-        // In case where <Terminate Arc> is started by <Request ARC Termination>
-        // need to clean up RequestArcInitiationAction.
-        // TODO: check conditions of power status by calling is_connected api
-        // to be added soon.
-        removeAction(RequestArcTerminationAction.class);
-        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
-                message.getDestination(), message.getSource(), false);
-        addAndStartAction(action);
+    Object getServiceLock() {
+        return mLock;
     }
 
-    private void handleGetCecVersion(HdmiCecMessage message) {
-        int version = mCecController.getVersion();
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
-                message.getSource(),
-                version);
-        sendCecCommand(cecMessage);
+    void setAudioStatus(boolean mute, int volume) {
+        // TODO: Hook up with AudioManager.
     }
 
-    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);
+    void announceSystemAudioModeChange(boolean enabled) {
+        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
+            invokeSystemAudioModeChange(listener, enabled);
         }
     }
 
-    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)) {
-                return true;
-            }
-        }
-        Slog.w(TAG, "Unsupported cec command:" + message);
-        return false;
+    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);
     }
 
     // Record class that monitors the event of the caller of being killed. Used to clean up
@@ -456,36 +505,36 @@
         }
     }
 
-    void addCecDevice(HdmiCecDeviceInfo info) {
-        mCecController.addDeviceInfo(info);
+    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
+        private final IHdmiDeviceEventListener mListener;
+
+        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mDeviceEventListenerRecords.remove(this);
+                mDeviceEventListeners.remove(mListener);
+            }
+        }
     }
 
-    // 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();
+    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
+        private IHdmiSystemAudioModeChangeListener mListener;
 
-        // 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);
-                        }
+        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
+            mListener = listener;
+        }
 
-                        // 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);
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSystemAudioModeChangeListenerRecords.remove(this);
+                mSystemAudioModeChangeListeners.remove(mListener);
+            }
+        }
     }
 
     private void enforceAccessPermission() {
@@ -496,9 +545,64 @@
         @Override
         public int[] getSupportedTypes() {
             enforceAccessPermission();
-            synchronized (mLock) {
-                return mLocalDevices;
+            // mLocalDevices is an unmodifiable list - no lock necesary.
+            int[] localDevices = new int[mLocalDevices.size()];
+            for (int i = 0; i < localDevices.length; ++i) {
+                localDevices[i] = mLocalDevices.get(i);
             }
+            return localDevices;
+        }
+
+        @Override
+        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiCecLocalDeviceTv tv = tv();
+                    if (tv == null) {
+                        Slog.w(TAG, "Local tv device not available");
+                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+                        return;
+                    }
+                    tv.deviceSelect(logicalAddress, callback);
+                }
+            });
+        }
+
+        @Override
+        public void portSelect(final int portId, final IHdmiControlCallback callback) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiCecLocalDeviceTv tv = tv();
+                    if (tv == null) {
+                        Slog.w(TAG, "Local tv device not available");
+                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+                        return;
+                    }
+                    tv.doManualPortSwitching(portId, callback);
+                }
+            });
+        }
+
+        @Override
+        public void sendKeyEvent(final int keyCode, final boolean isPressed) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    // TODO: sendKeyEvent is for TV device only for now. Allow other
+                    //       local devices of different types to use this as well.
+                    HdmiCecLocalDeviceTv tv = tv();
+                    if (tv == null) {
+                        Slog.w(TAG, "Local tv device not available");
+                        return;
+                    }
+                    tv.sendKeyEvent(keyCode, isPressed);
+                }
+            });
         }
 
         @Override
@@ -544,53 +648,98 @@
                 }
             });
         }
+
+        @Override
+        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.addDeviceEventListener(listener);
+                }
+            });
+        }
+
+        @Override
+        public List<HdmiPortInfo> getPortInfo() {
+            enforceAccessPermission();
+            return mPortInfo;
+        }
+
+        @Override
+        public boolean canChangeSystemAudioMode() {
+            enforceAccessPermission();
+            HdmiCecLocalDeviceTv tv = tv();
+            if (tv == null) {
+                return false;
+            }
+            return tv.hasSystemAudioDevice();
+        }
+
+        @Override
+        public boolean getSystemAudioMode() {
+            enforceAccessPermission();
+            HdmiCecLocalDeviceTv tv = tv();
+            if (tv == null) {
+                return false;
+            }
+            return tv.getSystemAudioMode();
+        }
+
+        @Override
+        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiCecLocalDeviceTv tv = tv();
+                    if (tv == null) {
+                        Slog.w(TAG, "Local tv device not available");
+                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+                        return;
+                    }
+                    tv.changeSystemAudioMode(enabled, callback);
+                }
+            });
+        }
+
+        @Override
+        public void addSystemAudioModeChangeListener(
+                final IHdmiSystemAudioModeChangeListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
+        }
+
+        @Override
+        public void removeSystemAudioModeChangeListener(
+                final IHdmiSystemAudioModeChangeListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
+        }
     }
 
-    private void oneTouchPlay(IHdmiControlCallback callback) {
-        if (hasAction(OneTouchPlayAction.class)) {
-            Slog.w(TAG, "oneTouchPlay already in progress");
-            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
-            return;
-        }
-        HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+    @ServiceThreadOnly
+    private void oneTouchPlay(final IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        HdmiCecLocalDevicePlayback source = playback();
         if (source == null) {
             Slog.w(TAG, "Local playback device not available");
             invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
             return;
         }
-        // TODO: Consider the case of multiple TV sets. For now we always direct the command
-        //       to the primary one.
-        OneTouchPlayAction action = OneTouchPlayAction.create(this,
-                source.getDeviceInfo().getLogicalAddress(),
-                source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback);
-        if (action == null) {
-            Slog.w(TAG, "Cannot initiate oneTouchPlay");
-            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
-            return;
-        }
-        addAndStartAction(action);
+        source.oneTouchPlay(callback);
     }
 
-    private void queryDisplayStatus(IHdmiControlCallback callback) {
-        if (hasAction(DevicePowerStatusAction.class)) {
-            Slog.w(TAG, "queryDisplayStatus already in progress");
-            invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
-            return;
-        }
-        HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+    @ServiceThreadOnly
+    private void queryDisplayStatus(final IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        HdmiCecLocalDevicePlayback source = playback();
         if (source == null) {
             Slog.w(TAG, "Local playback device not available");
             invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
             return;
         }
-        DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
-                source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback);
-        if (action == null) {
-            Slog.w(TAG, "Cannot initiate queryDisplayStatus");
-            invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
-            return;
-        }
-        addAndStartAction(action);
+        source.queryDisplayStatus(callback);
     }
 
     private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
@@ -620,6 +769,61 @@
         }
     }
 
+    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
+        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
+        try {
+            listener.asBinder().linkToDeath(record, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Listener already died");
+            return;
+        }
+        synchronized (mLock) {
+            mDeviceEventListeners.add(listener);
+            mDeviceEventListenerRecords.add(record);
+        }
+    }
+
+    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
+        synchronized (mLock) {
+            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
+                try {
+                    listener.onStatusChanged(device, activated);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report device event:" + e);
+                }
+            }
+        }
+    }
+
+    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
+        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
+                listener);
+        try {
+            listener.asBinder().linkToDeath(record, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Listener already died");
+            return;
+        }
+        synchronized (mLock) {
+            mSystemAudioModeChangeListeners.add(listener);
+            mSystemAudioModeChangeListenerRecords.add(record);
+        }
+    }
+
+    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
+        synchronized (mLock) {
+            for (SystemAudioModeChangeListenerRecord record :
+                    mSystemAudioModeChangeListenerRecords) {
+                if (record.mListener.asBinder() == listener) {
+                    listener.asBinder().unlinkToDeath(record, 0);
+                    mSystemAudioModeChangeListenerRecords.remove(record);
+                    break;
+                }
+            }
+            mSystemAudioModeChangeListeners.remove(listener);
+        }
+    }
+
     private void invokeCallback(IHdmiControlCallback callback, int result) {
         try {
             callback.onComplete(result);
@@ -627,4 +831,48 @@
             Slog.e(TAG, "Invoking callback failed:" + e);
         }
     }
+
+    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
+            boolean enabled) {
+        try {
+            listener.onStatusChanged(enabled);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
+
+    private void announceHotplugEvent(int portId, boolean connected) {
+        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
+        synchronized (mLock) {
+            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
+                invokeHotplugEventListenerLocked(listener, event);
+            }
+        }
+    }
+
+    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
+            HdmiHotplugEvent event) {
+        try {
+            listener.onReceived(event);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
+        }
+    }
+
+    private static boolean hasSameTopPort(int path1, int path2) {
+        return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
+                == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
+    }
+
+    private HdmiCecLocalDeviceTv tv() {
+        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV);
+    }
+
+    private HdmiCecLocalDevicePlayback playback() {
+        return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+    }
+
+    AudioManager getAudioManager() {
+        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+    }
 }
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..9b7cc8d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 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;
+    }
+
+    /**
+     * Convert integer array to list of {@link Integer}.
+     *
+     * <p>The result is immutable.
+     *
+     * @param is integer array
+     * @return {@link List} instance containing the elements in the given array
+     */
+    static List<Integer> asImmutableList(final int[] is) {
+        ArrayList<Integer> list = new ArrayList<>(is.length);
+        for (int type : is) {
+            list.add(type);
+        }
+        return Collections.unmodifiableList(list);
+    }
+
+    /**
+     * 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);
+    }
+
+    static <T> List<T> sparseArrayToList(SparseArray<T> array) {
+        ArrayList<T> list = new ArrayList<>();
+        for (int i = 0; i < array.size(); ++i) {
+            list.add(array.valueAt(i));
+        }
+        return list;
+    }
+
+}
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..647cc88
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -0,0 +1,247 @@
+/*
+ * 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 source {@link HdmiCecLocalDevice} instance
+     */
+    HotplugDetectionAction(HdmiCecLocalDevice source) {
+        super(source);
+    }
+
+    @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();
+        }
+    }
+
+    /**
+     * Start device polling immediately.
+     */
+    void pollAllDevicesNow() {
+        // Clear existing timer to avoid overlapped execution
+        mActionTimer.clearTimerMessage();
+
+        mTimeoutCount = 0;
+        mState = STATE_WAIT_FOR_NEXT_POLLING;
+        pollAllDevices();
+
+        addTimer(mState, POLLING_INTERVAL_MS);
+    }
+
+    // This method is called every 5 seconds.
+    private void pollDevices() {
+        // All device check called every 15 seconds.
+        if (mTimeoutCount == 0) {
+            pollAllDevices();
+        } else {
+            if (tv().getSystemAudioMode()) {
+                pollAudioSystem();
+            }
+        }
+
+        addTimer(mState, POLLING_INTERVAL_MS);
+    }
+
+    private void pollAllDevices() {
+        Slog.v(TAG, "Poll all devices.");
+
+        pollDevices(new DevicePollingCallback() {
+            @Override
+            public void onPollingFinished(List<Integer> ackedAddress) {
+                checkHotplug(ackedAddress, false);
+            }
+        }, HdmiConstants.POLL_ITERATION_IN_ORDER
+                | HdmiConstants.POLL_STRATEGY_REMOTES_DEVICES, POLL_RETRY_COUNT);
+    }
+
+    private void pollAudioSystem() {
+        Slog.v(TAG, "Poll audio system.");
+
+        pollDevices(new DevicePollingCallback() {
+            @Override
+            public void onPollingFinished(List<Integer> ackedAddress) {
+                checkHotplug(ackedAddress, true);
+            }
+        }, HdmiConstants.POLL_ITERATION_IN_ORDER
+                | HdmiConstants.POLL_STRATEGY_SYSTEM_AUDIO, POLL_RETRY_COUNT);
+    }
+
+    private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
+        BitSet currentInfos = infoListToBitSet(tv().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) {
+        // Send <Give Physical Address>.
+        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
+                addedAddress));
+    }
+
+    private void removeDevice(int removedAddress) {
+        // TODO: move the following logic to local device once move many logic to
+        //       local device.
+        mayChangeRoutingPath(removedAddress);
+        mayCancelDeviceSelect(removedAddress);
+        mayCancelOneTouchRecord(removedAddress);
+        mayDisableSystemAudioAndARC(removedAddress);
+
+        tv().removeCecDevice(removedAddress);
+    }
+
+    private void mayChangeRoutingPath(int address) {
+        // TODO: if removed address is current active source, it should change active source
+        // path new one. we can keep previous selection or can choose default input,
+        // such as internal tuner. Consider send intent to app so that app
+        // can handle it.
+    }
+
+    private void mayCancelDeviceSelect(int address) {
+        List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
+        if (actions.isEmpty()) {
+            return;
+        }
+
+        // Should ave only one Device Select Action
+        DeviceSelectAction action = actions.get(0);
+        if (action.getTargetAddress() == address) {
+            removeAction(DeviceSelectAction.class);
+        }
+    }
+
+    private void mayCancelOneTouchRecord(int address) {
+        // TODO: implement this.
+    }
+
+    private void mayDisableSystemAudioAndARC(int address) {
+        if (HdmiCec.getTypeFromAddress(address) != HdmiCec.DEVICE_AUDIO_SYSTEM) {
+            return;
+        }
+
+        // Turn off system audio mode.
+        tv().setSystemAudioMode(false);
+        if (tv().getArcStatus()) {
+            addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 156bbbe..4a49f09 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -48,6 +48,7 @@
 
     private final int mDeviceLogicalAddress;
     private final int mDevicePhysicalAddress;
+    private final boolean mRequireRoutingChange;
 
     private int mVendorId;
     private String mDisplayName;
@@ -55,25 +56,46 @@
     /**
      * Constructor.
      *
-     * @param service {@link HdmiControlService} instance
-     * @param sourceAddress logical address to be used as source address
+     * @param source {@link HdmiCecLocalDevice} instance
      * @param deviceLogicalAddress logical address of the device in interest
      * @param devicePhysicalAddress physical address of the device in interest
+     * @param requireRoutingChange whether to initiate routing change or not
      */
-    NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress,
-            int devicePhysicalAddress) {
-        super(service, sourceAddress);
+    NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
+            int devicePhysicalAddress, boolean requireRoutingChange) {
+        super(source);
         mDeviceLogicalAddress = deviceLogicalAddress;
         mDevicePhysicalAddress = devicePhysicalAddress;
         mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+        mRequireRoutingChange = requireRoutingChange;
     }
 
     @Override
     public boolean start() {
-        sendCommand(
-                HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress,
-                        mDeviceLogicalAddress));
+        if (HdmiCec.getTypeFromAddress(getSourceAddress()) == HdmiCec.DEVICE_AUDIO_SYSTEM) {
+            if (tv().getAvrDeviceInfo() == null) {
+                // TODO: Start system audio initiation action
+            }
+
+            // If new device is connected through ARC enabled port,
+            // initiates ARC channel establishment.
+            if (tv().isConnectedToArcPort(mDevicePhysicalAddress)) {
+                addAndStartAction(new RequestArcInitiationAction(localDevice(),
+                                mDeviceLogicalAddress));
+            }
+        }
+
+        if (mRequireRoutingChange) {
+            startRoutingChange();
+        }
+
         mState = STATE_WAITING_FOR_SET_OSD_NAME;
+        if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_SET_OSD_NAME)) {
+            return true;
+        }
+
+        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(),
+                mDeviceLogicalAddress));
         addTimer(mState, TIMEOUT_MS);
         return true;
     }
@@ -99,13 +121,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 +133,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 +141,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,8 +152,38 @@
         return false;
     }
 
+    private void startRoutingChange() {
+        // Stop existing routing control.
+        removeAction(RoutingControlAction.class);
+
+        // Send routing change. The the address is a path of the active port.
+        int newPath = toTopMostPortPath(mDevicePhysicalAddress);
+        sendCommand(HdmiCecMessageBuilder.buildRoutingChange(getSourceAddress(),
+                localDevice().getActivePath(), newPath));
+        addAndStartAction(new RoutingControlAction(localDevice(),
+                localDevice().pathToPortId(newPath), null));
+    }
+
+    private static int toTopMostPortPath(int physicalAddress) {
+        return physicalAddress & HdmiConstants.ROUTING_PATH_TOP_MASK;
+    }
+
+    private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
+        HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode);
+        if (message != null) {
+            return processCommand(message);
+        }
+        return false;
+    }
+
     private void requestVendorId() {
-        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress,
+        // 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(getSourceAddress(),
                 mDeviceLogicalAddress));
         addTimer(mState, TIMEOUT_MS);
     }
@@ -143,7 +192,7 @@
         if (mDisplayName == null) {
             mDisplayName = HdmiCec.getDefaultDeviceName(mDeviceLogicalAddress);
         }
-        mService.addDeviceInfo(new HdmiCecDeviceInfo(
+        tv().addCecDevice(new HdmiCecDeviceInfo(
                 mDeviceLogicalAddress, mDevicePhysicalAddress,
                 HdmiCec.getTypeFromAddress(mDeviceLogicalAddress),
                 mVendorId, mDisplayName));
@@ -156,7 +205,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/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 69fad13..e0a3a8b 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -30,7 +30,7 @@
  * <p>Package-private, accessed by {@link HdmiControlService} only.
  */
 
-public final class OneTouchPlayAction extends FeatureAction {
+final class OneTouchPlayAction extends FeatureAction {
     private static final String TAG = "OneTouchPlayAction";
 
     // State in which the action is waiting for <Report Power Status>. In normal situation
@@ -48,34 +48,32 @@
     // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
     private static final int LOOP_COUNTER_MAX = 10;
 
-    private final int mSourcePath;
     private final int mTargetAddress;
     private final IHdmiControlCallback mCallback;
 
     private int mPowerStatusCounter = 0;
 
     // Factory method. Ensures arguments are valid.
-    static OneTouchPlayAction create(HdmiControlService service, int sourceAddress,
-            int sourcePath, int targetAddress, IHdmiControlCallback callback) {
-        if (service == null || callback == null) {
+    static OneTouchPlayAction create(HdmiCecLocalDevice source,
+            int targetAddress, IHdmiControlCallback callback) {
+        if (source == null || callback == null) {
             Slog.e(TAG, "Wrong arguments");
             return null;
         }
-        return new OneTouchPlayAction(service, sourceAddress, sourcePath, targetAddress, callback);
+        return new OneTouchPlayAction(source, targetAddress,
+                callback);
     }
 
-    private OneTouchPlayAction(HdmiControlService service, int sourceAddress, int sourcePath,
-            int targetAddress, IHdmiControlCallback callback) {
-        super(service, sourceAddress);
-        mSourcePath = sourcePath;
+    private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress,
+            IHdmiControlCallback callback) {
+        super(localDevice);
         mTargetAddress = targetAddress;
         mCallback = callback;
     }
 
     @Override
     boolean start() {
-        mService.sendCecCommand(
-                HdmiCecMessageBuilder.buildTextViewOn(mSourceAddress, mTargetAddress));
+        sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
         broadcastActiveSource();
         queryDevicePowerStatus();
         mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
@@ -84,13 +82,12 @@
     }
 
     private void broadcastActiveSource() {
-        mService.sendCecCommand(
-                HdmiCecMessageBuilder.buildActiveSource(mSourceAddress, mSourcePath));
+        sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
     }
 
     private void queryDevicePowerStatus() {
-        mService.sendCecCommand(
-                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mSourceAddress, mTargetAddress));
+        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                mTargetAddress));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 05614a4..a2e08f1 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -35,37 +35,22 @@
     /**
      * @Constructor
      *
-     * @param service {@link HdmiControlService} instance
-     * @param sourceAddress logical address to be used as source address. It should
-     *                      TV type
+     * @param source {@link HdmiCecLocalDevice} instance
      * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
      * @throw IllegalArugmentException if device type of sourceAddress and avrAddress
      *                      is invalid
      */
-    RequestArcAction(HdmiControlService service, int sourceAddress, int avrAddress) {
-        super(service, sourceAddress);
-        verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
-        verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+    RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source);
+        HdmiUtils.verifyAddressType(getSourceAddress(), 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();
@@ -85,9 +70,9 @@
 
     protected final void disableArcTransmission() {
         // Start Set ARC Transmission State action.
-        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(mService,
-                mSourceAddress, mAvrAddress, false);
-        mService.addAndStartAction(action);
+        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(localDevice(),
+                mAvrAddress, false);
+        addAndStartAction(action);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 343aff7..55fb65a 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -31,18 +31,18 @@
      *
      * For more details look at {@link RequestArcAction#RequestArcAction}.
      */
-    RequestArcInitiationAction(HdmiControlService service, int sourceAddress, int avrAddress) {
-        super(service, sourceAddress, avrAddress);
+    RequestArcInitiationAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source, avrAddress);
     }
 
     @Override
     boolean start() {
-        HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress,
-                mAvrAddress);
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                getSourceAddress(), mAvrAddress);
         sendCommand(command, new HdmiControlService.SendMessageCallback() {
             @Override
             public void onSendCompleted(int error) {
-                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
                     mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
                     addTimer(mState, TIMEOUT_MS);
                 } else {
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index d4a35f8..62ca8f6 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -31,18 +31,18 @@
      *
      * @see RequestArcAction#RequestArcAction
      */
-    RequestArcTerminationAction(HdmiControlService service, int sourceAddress, int avrAddress) {
-        super(service, sourceAddress, avrAddress);
+    RequestArcTerminationAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source, avrAddress);
     }
 
     @Override
     boolean start() {
         HdmiCecMessage command =
-                HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress);
+                HdmiCecMessageBuilder.buildRequestArcTermination(getSourceAddress(), mAvrAddress);
         sendCommand(command, new HdmiControlService.SendMessageCallback() {
             @Override
             public void onSendCompleted(int error) {
-                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
                     mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
                     addTimer(mState, TIMEOUT_MS);
                 } else {
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
new file mode 100644
index 0000000..0d657b2
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.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.server.hdmi;
+
+import android.annotation.Nullable;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Feature action for routing control. Exchanges routing-related commands with other devices
+ * to determine the new active source.
+ *
+ * <p>This action is initiated by various cases:
+ * <ul>
+ * <li> Manual TV input switching
+ * <li> Routing change of a CEC switch other than TV
+ * <li> New CEC device at the tail of the active routing path
+ * <li> Removed CEC device from the active routing path
+ * <li> Routing at CEC enable time
+ * </ul>
+ */
+final class RoutingControlAction extends FeatureAction {
+    private static final String TAG = "RoutingControlAction";
+
+    // State in which we wait for <Routing Information> to arrive. If timed out, we use the
+    // latest routing path to set the new active source.
+    private final static int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
+
+    // State in which we wait for <Report Power Status> in response to <Give Device Power Status>
+    // we have sent. If the response tells us the device power is on, we send <Set Stream Path>
+    // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly
+    // just show the blank screen.
+    private final static int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2;
+
+    // Time out in millseconds used for <Routing Information>
+    private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000;
+
+    // Time out in milliseconds used for <Report Power Status>
+    private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000;
+
+    @Nullable private final IHdmiControlCallback mCallback;
+
+    // The latest routing path. Updated by each <Routing Information> from CEC switches.
+    private int mCurrentRoutingPath;
+
+    RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) {
+        super(localDevice);
+        mCallback = callback;
+        mCurrentRoutingPath = path;
+    }
+
+    @Override
+    public boolean start() {
+        mState = STATE_WAIT_FOR_ROUTING_INFORMATION;
+        addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS);
+        return true;
+    }
+
+    @Override
+    public boolean processCommand(HdmiCecMessage cmd) {
+        int opcode = cmd.getOpcode();
+        byte[] params = cmd.getParams();
+        if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION
+                && opcode == HdmiCec.MESSAGE_ROUTING_INFORMATION) {
+            // Keep updating the physicalAddress as we receive <Routing Information>.
+            // If the routing path doesn't belong to the currently active one, we should
+            // ignore it since it might have come from other routing change sequence.
+            int routingPath = HdmiUtils.twoBytesToInt(params);
+            if (isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) {
+                return true;
+            }
+            mCurrentRoutingPath = routingPath;
+            // Stop possible previous routing change sequence if in progress.
+            removeAction(RoutingControlAction.class);
+            addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS);
+            return true;
+        } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
+                  && opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS) {
+            handleReportPowerStatus(cmd.getParams()[0]);
+            return true;
+        }
+        return false;
+    }
+
+    private void handleReportPowerStatus(int devicePowerStatus) {
+        int tvPowerStatus = getTvPowerStatus();
+        if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) {
+            if (isPowerStatusOnOrTransientToOn(devicePowerStatus)) {
+                sendSetStreamPath();
+            } else {
+                // The whole action should be stopped here if the device is in standby mode.
+                // We don't attempt to wake it up by sending <Set Stream Path>.
+            }
+            invokeCallback(HdmiCec.RESULT_SUCCESS);
+            finish();
+          } else {
+              // TV is going into standby mode.
+              // TODO: Figure out what to do.
+          }
+     }
+
+    private int getTvPowerStatus() {
+        // TODO: Obtain TV power status.
+        return HdmiCec.POWER_STATUS_ON;
+    }
+
+    private static boolean isPowerStatusOnOrTransientToOn(int status) {
+        return status == HdmiCec.POWER_STATUS_ON || status == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
+    }
+
+    private void sendSetStreamPath() {
+        sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(),
+                mCurrentRoutingPath));
+    }
+
+    private static boolean isInActiveRoutingPath(int activePath, int newPath) {
+        // Check each nibble of the currently active path and the new path till the position
+        // where the active nibble is not zero. For (activePath, newPath),
+        // (1.1.0.0, 1.0.0.0) -> true, new path is a parent
+        // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant
+        // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling
+        // (1.0.0.0, 2.0.0.0) -> false, in a completely different path
+        for (int i = 12; i >= 0; i -= 4) {
+            int nibbleActive = (activePath >> i) & 0xF;
+            if (nibbleActive == 0) {
+                break;
+            }
+            int nibbleNew = (newPath >> i) & 0xF;
+            if (nibbleNew == 0) {
+                break;
+            }
+            if (nibbleActive != nibbleNew) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void handleTimerEvent(int timeoutState) {
+        if (mState != timeoutState || mState == STATE_NONE) {
+            Slog.w("CEC", "Timer in a wrong state. Ignored.");
+            return;
+        }
+        switch (timeoutState) {
+            case STATE_WAIT_FOR_ROUTING_INFORMATION:
+                HdmiCecDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath);
+                if (device == null) {
+                    maybeChangeActiveInput(tv().pathToPortId(mCurrentRoutingPath));
+                } else {
+                    // TODO: Also check followings and then proceed:
+                    //       if routing change was neither triggered by TV at CEC enable time, nor
+                    //       at the detection of new device at the end of the active routing path, nor
+                    //       by TV power on with HDMI input as the active signal source.
+                    int deviceLogicalAddress = device.getLogicalAddress();
+                    queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
+                        @Override
+                        public void onSendCompleted(int error) {
+                            handlDevicePowerStatusAckResult(error == HdmiCec.RESULT_SUCCESS);
+                        }
+                    });
+                }
+                return;
+            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
+                int tvPowerStatus = getTvPowerStatus();
+                if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) {
+                    if (!maybeChangeActiveInput(localDevice().pathToPortId(mCurrentRoutingPath))) {
+                        sendSetStreamPath();
+                    }
+                }
+                invokeCallback(HdmiCec.RESULT_SUCCESS);
+                finish();
+                return;
+        }
+    }
+
+    // Called whenever an HDMI input of the TV shall become the active input.
+    private boolean maybeChangeActiveInput(int path) {
+        if (localDevice().getActivePortId() == localDevice().pathToPortId(path)) {
+            return false;
+        }
+        // TODO: Remember the currently active input
+        //       if PAP/PIP is active, move the focus to the right window, otherwise switch
+        //       the port.
+        //       Show the OSD input change banner.
+        return true;
+    }
+
+    private void queryDevicePowerStatus(int address, SendMessageCallback callback) {
+        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address),
+                callback);
+    }
+
+    private void handlDevicePowerStatusAckResult(boolean acked) {
+        if (acked) {
+            mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
+            addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
+        } else {
+            maybeChangeActiveInput(localDevice().pathToPortId(mCurrentRoutingPath));
+        }
+    }
+
+    private void invokeCallback(int result) {
+        if (mCallback == null) {
+            return;
+        }
+        try {
+            mCallback.onComplete(result);
+        } catch (RemoteException e) {
+            // Do nothing.
+        }
+    }
+}
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..5d81251
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -0,0 +1,143 @@
+/*
+ * 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 static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+/**
+ * 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;
+
+    // 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 source {@link HdmiCecLocalDevice} instance
+     * @param targetAddress logical address of the device to send the keys to
+     * @param keyCode remote control key code as defined in {@link KeyEvent}
+     */
+    SendKeyAction(HdmiCecLocalDevice source, int targetAddress, int keyCode) {
+        super(source);
+        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}
+     */
+    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(getSourceAddress(),
+                mTargetAddress, keyCodeAndParam));
+    }
+
+    private void sendKeyUp() {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+                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) {
+        return HdmiCecKeycode.androidKeyToCecKey(keyCode);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index e3525d8..cdedd6b 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -39,28 +39,18 @@
     /**
      * @Constructor
      *
-     * @param service an instance of {@link HdmiControlService}
-     * @param sourceAddress logical address to be used as source address
+     * @param source {@link HdmiCecLocalDevice} instance
      * @param enabled whether to enable ARC Transmission channel
      */
-    SetArcTransmissionStateAction(HdmiControlService service, int sourceAddress, int avrAddress,
+    SetArcTransmissionStateAction(HdmiCecLocalDevice source, int avrAddress,
             boolean enabled) {
-        super(service, sourceAddress);
-        verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
-        verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+        super(source);
+        HdmiUtils.verifyAddressType(getSourceAddress(), 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) {
@@ -74,11 +64,11 @@
 
     private void sendReportArcInitiated() {
         HdmiCecMessage command =
-                HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress);
+                HdmiCecMessageBuilder.buildReportArcInitiated(getSourceAddress(), mAvrAddress);
         sendCommand(command, new HdmiControlService.SendMessageCallback() {
             @Override
             public void onSendCompleted(int error) {
-                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
                     // Enable ARC status immediately after sending <Report Arc Initiated>.
                     // If AVR responds with <Feature Abort>, disable ARC status again.
                     // This is different from spec that says that turns ARC status to
@@ -102,14 +92,14 @@
     }
 
     private void setArcStatus(boolean enabled) {
-        boolean wasEnabled = mService.setArcStatus(enabled);
+        boolean wasEnabled = tv().setArcStatus(enabled);
         Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);
 
         // If enabled before and set to "disabled" and send <Report Arc Terminated> to
         // av reciever.
         if (!enabled && wasEnabled) {
-            sendCommand(
-                    HdmiCecMessageBuilder.buildReportArcTerminated(mSourceAddress, mAvrAddress));
+            sendCommand(HdmiCecMessageBuilder.buildReportArcTerminated(getSourceAddress(),
+                    mAvrAddress));
         }
     }
 
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..959a38e
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -0,0 +1,161 @@
+/*
+ * 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.annotation.Nullable;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * 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;
+
+    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;
+
+    @Nullable private final IHdmiControlCallback mCallback;
+
+    private int mSendRetryCount = 0;
+
+    /**
+     * Constructor
+     *
+     * @param source {@link HdmiCecLocalDevice} instance
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @param callback callback interface to be notified when it's done
+     * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
+     */
+    SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus,
+            IHdmiControlCallback callback) {
+        super(source);
+        HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+        mAvrLogicalAddress = avrAddress;
+        mTargetAudioStatus = targetStatus;
+        mCallback = callback;
+    }
+
+    protected void sendSystemAudioModeRequest() {
+        int avrPhysicalAddress = tv().getAvrDeviceInfo().getPhysicalAddress();
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+                getSourceAddress(),
+                mAvrLogicalAddress, avrPhysicalAddress, mTargetAudioStatus);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiConstants.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
+                    addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
+                } else {
+                    setSystemAudioMode(false);
+                    finishWithCallback(HdmiCec.RESULT_EXCEPTION);
+                }
+            }
+        });
+    }
+
+    private void handleSendSystemAudioModeRequestTimeout() {
+        if (!mTargetAudioStatus  // Don't retry for Off case.
+                || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) {
+            setSystemAudioMode(false);
+            finishWithCallback(HdmiCec.RESULT_TIMEOUT);
+            return;
+        }
+        sendSystemAudioModeRequest();
+    }
+
+    protected void setSystemAudioMode(boolean mode) {
+        tv().setSystemAudioMode(mode);
+    }
+
+    @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);
+                    startAudioStatusAction();
+                    return true;
+                } else {
+                    // Unexpected response, consider the request is newly initiated by AVR.
+                    // To return 'false' will initiate new SystemAudioActionFromAvr by the control
+                    // service.
+                    finishWithCallback(HdmiCec.RESULT_EXCEPTION);
+                    return false;
+                }
+            default:
+                return false;
+        }
+    }
+
+    protected void startAudioStatusAction() {
+        addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallback));
+        finish();
+    }
+
+    protected void removeSystemAudioActionInProgress() {
+        removeActionExcept(SystemAudioActionFromTv.class, this);
+        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;
+        }
+    }
+
+    // TODO: if IHdmiControlCallback is general to other FeatureAction,
+    //       move it into FeatureAction.
+    protected void finishWithCallback(int returnCode) {
+        if (mCallback != null) {
+            try {
+                mCallback.onComplete(returnCode);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to invoke callback.", e);
+            }
+        }
+        finish();
+    }
+}
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..9d34589
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -0,0 +1,70 @@
+/*
+ * 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.IHdmiControlCallback;
+
+/**
+ * Feature action that handles System Audio initiated by AVR devices.
+ */
+final class SystemAudioActionFromAvr extends SystemAudioAction {
+    /**
+     * Constructor
+     *
+     * @param source {@link HdmiCecLocalDevice} instance
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @param callback callback interface to be notified when it's done
+     * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid
+     */
+    SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress,
+            boolean targetStatus, IHdmiControlCallback callback) {
+        super(source, avrAddress, targetStatus, callback);
+        HdmiUtils.verifyAddressType(getSourceAddress(), HdmiCec.DEVICE_TV);
+    }
+
+    @Override
+    boolean start() {
+        removeSystemAudioActionInProgress();
+        handleSystemAudioActionFromAvr();
+        return true;
+    }
+
+    private void handleSystemAudioActionFromAvr() {
+        if (mTargetAudioStatus == tv().getSystemAudioMode()) {
+            finishWithCallback(HdmiCec.RESULT_SUCCESS);
+            return;
+        }
+        if (tv().isInPresetInstallationMode()) {
+            sendCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                    getSourceAddress(), 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);
+            startAudioStatusAction();
+        } else {
+            setSystemAudioMode(false);
+            finishWithCallback(HdmiCec.RESULT_SUCCESS);
+        }
+    }
+}
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..2d8f3fc
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -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.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.IHdmiControlCallback;
+
+
+/**
+ * Feature action that handles System Audio initiated by TV devices.
+ */
+final class SystemAudioActionFromTv extends SystemAudioAction {
+    /**
+     * Constructor
+     *
+     * @param sourceAddress {@link HdmiCecLocalDevice} instance
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @param callback callback interface to be notified when it's done
+     * @throw IllegalArugmentException if device type of tvAddress is invalid
+     */
+    SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress,
+            boolean targetStatus, IHdmiControlCallback callback) {
+        super(sourceAddress, avrAddress, targetStatus, callback);
+        HdmiUtils.verifyAddressType(getSourceAddress(), 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/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
new file mode 100644
index 0000000..6f4164b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Action to initiate system audio once AVR is detected on Device discovery action.
+ */
+final class SystemAudioAutoInitiationAction extends FeatureAction {
+    private final int mAvrAddress;
+
+    // State that waits for <System Audio Mode Status> once send
+    // <Give System Audio Mode Status> to AV Receiver.
+    private static final int STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS = 1;
+
+    SystemAudioAutoInitiationAction(HdmiCecLocalDevice source, int avrAddress) {
+        super(source);
+        mAvrAddress = avrAddress;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS;
+
+        addTimer(mState, TIMEOUT_MS);
+        sendGiveSystemAudioModeStatus();
+        return true;
+    }
+
+    private void sendGiveSystemAudioModeStatus() {
+        sendCommand(HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(getSourceAddress(),
+                mAvrAddress), new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != HdmiConstants.SEND_RESULT_SUCCESS) {
+                    tv().setSystemAudioMode(false);
+                    finish();
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
+                handleSystemAudioModeStatusMessage();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void handleSystemAudioModeStatusMessage() {
+        // If the last setting is system audio, turn on system audio whatever AVR status is.
+        if (tv().getSystemAudioMode()) {
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true, null));
+            }
+        } else {
+            // If the last setting is non-system audio, turn off system audio mode
+            // and update system audio status (volume or mute).
+            tv().setSystemAudioMode(false);
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioStatusAction(tv(), mAvrAddress, null));
+            }
+        }
+        finish();
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
+        switch (mState) {
+            case STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS:
+                handleSystemAudioModeStatusTimeout();
+                break;
+        }
+    }
+
+    private void handleSystemAudioModeStatusTimeout() {
+        if (tv().getSystemAudioMode()) {
+            if (canChangeSystemAudio()) {
+                addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true, null));
+            }
+        } else {
+            tv().setSystemAudioMode(false);
+        }
+        finish();
+    }
+
+    private boolean canChangeSystemAudio() {
+        return !(tv().hasAction(SystemAudioActionFromTv.class)
+               || tv().hasAction(SystemAudioActionFromAvr.class));
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
new file mode 100644
index 0000000..ecb158b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -0,0 +1,134 @@
+/*
+ * 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.annotation.Nullable;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Action to update audio status (volume or mute) of audio amplifier
+ */
+final class SystemAudioStatusAction extends FeatureAction {
+    private static final String TAG = "SystemAudioStatusAction";
+
+    // State that waits for <ReportAudioStatus>.
+    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
+
+    private final int mAvrAddress;
+    @Nullable private final IHdmiControlCallback mCallback;
+
+    SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
+            IHdmiControlCallback callback) {
+        super(source);
+        mAvrAddress = avrAddress;
+        mCallback = callback;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
+        addTimer(mState, TIMEOUT_MS);
+        sendGiveAudioStatus();
+        return true;
+    }
+
+    private void sendGiveAudioStatus() {
+        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
+                new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != HdmiConstants.SEND_RESULT_SUCCESS) {
+                    handleSendGiveAudioStatusFailure();
+                }
+            }
+        });
+    }
+
+    private void handleSendGiveAudioStatusFailure() {
+        // Inform to all application that the audio status (volumn, mute) of
+        // the audio amplifier is unknown.
+        tv().setAudioStatus(false, HdmiConstants.UNKNOWN_VOLUME);
+
+        int uiCommand = tv().getSystemAudioMode()
+                ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
+                : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
+        sendUserControlPressedAndReleased(mAvrAddress, uiCommand);
+
+        // Still return SUCCESS to callback.
+        finishWithCallback(HdmiCec.RESULT_SUCCESS);
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                handleReportAudioStatus(cmd);
+                return true;
+        }
+
+        return false;
+    }
+
+    private void handleReportAudioStatus(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        if (params.length > 0) {
+            boolean mute = (params[0] & 0x80) == 0x80;
+            int volume = params[0] & 0x7F;
+            tv().setAudioStatus(mute, volume);
+
+            if ((tv().getSystemAudioMode() && mute) || (!tv().getSystemAudioMode() && !mute)) {
+                // Toggle AVR's mute status to match with the system audio status.
+                sendUserControlPressedAndReleased(mAvrAddress, HdmiConstants.UI_COMMAND_MUTE);
+            }
+            finishWithCallback(HdmiCec.RESULT_SUCCESS);
+        } else {
+            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
+            handleSendGiveAudioStatusFailure();
+            return;
+        }
+    }
+
+    private void finishWithCallback(int returnCode) {
+        if (mCallback != null) {
+            try {
+                mCallback.onComplete(returnCode);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to invoke callback.", e);
+            }
+        }
+        finish();
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
+        handleSendGiveAudioStatusFailure();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/VendorSpecificAction.java b/services/core/java/com/android/server/hdmi/VendorSpecificAction.java
new file mode 100644
index 0000000..c954b50
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/VendorSpecificAction.java
@@ -0,0 +1,54 @@
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCecMessage;
+
+/**
+ * Handles vendor-specific commands that require a sequence of command exchange,
+ * or need to manage some states to complete the processing.
+ */
+public class VendorSpecificAction extends FeatureAction {
+
+    // Sample state this action can be in.
+    private static final int STATE_1 = 1;
+    private static final int STATE_2 = 2;
+
+    VendorSpecificAction(HdmiCecLocalDevice source) {
+        super(source);
+        // Modify the constructor if additional arguments are necessary.
+    }
+
+    @Override
+    boolean start() {
+        // Do initialization step and update the state accordingly here.
+        mState = STATE_1;
+        addTimer(STATE_1, TIMEOUT_MS);
+        return true;
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        // Returns true if the command was consumed. Otherwise return false for other
+        // actions in progress can be given its turn to process it.
+        return false;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        // Ignore the timer event if the current state and the state this event should be
+        // handled in are different. Could be an outdated event which should have been cleared by
+        // calling {@code mActionTimer.clearTimerMessage()}.
+        if (mState != state) {
+            return;
+        }
+
+        switch (state) {
+            case STATE_1:
+                mState = STATE_2;
+                addTimer(STATE_2, TIMEOUT_MS);
+                break;
+            case STATE_2:
+                finish();
+                break;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
new file mode 100644
index 0000000..07c72f7
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
@@ -0,0 +1,222 @@
+/*
+ * 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 static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Feature action that transmits volume change to Audio Receiver.
+ * <p>
+ * This action is created when a user pressed volume up/down. However, Since Android only provides a
+ * listener for delta of some volume change, we will set a target volume, and check reported volume
+ * from Audio Receiver(AVR). If TV receives no &lt;Report Audio Status&gt; from AVR, this action
+ * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms).
+ */
+final class VolumeControlAction extends FeatureAction {
+    private static final String TAG = "VolumeControlAction";
+
+    private static final int VOLUME_MUTE = 101;
+    private static final int VOLUME_RESTORE = 102;
+    private static final int MAX_VOLUME = 100;
+    private static final int MIN_VOLUME = 0;
+
+    // State where to wait for <Report Audio Status>
+    private static final int STATE_WAIT_FOR_REPORT_VOLUME_STATUS = 1;
+
+    // Maximum count of time out used to finish volume action.
+    private static final int VOLUME_CHANGE_TIMEOUT_MAX_COUNT = 2;
+
+    private final int mAvrAddress;
+    private final int mTargetVolume;
+    private final boolean mIsVolumeUp;
+    private int mTimeoutCount;
+
+    /**
+     * Create a {@link VolumeControlAction} for mute/restore change
+     *
+     * @param source source device sending volume change
+     * @param avrAddress address of audio receiver
+     * @param mute whether to mute sound or not. {@code true} for mute on; {@code false} for mute
+     *            off, i.e restore volume
+     * @return newly created {@link VolumeControlAction}
+     */
+    public static VolumeControlAction ofMute(HdmiCecLocalDevice source, int avrAddress,
+            boolean mute) {
+        return new VolumeControlAction(source, avrAddress, mute ? VOLUME_MUTE : VOLUME_RESTORE,
+                false);
+    }
+
+    /**
+     * Create a {@link VolumeControlAction} for volume up/down change
+     *
+     * @param source source device sending volume change
+     * @param avrAddress address of audio receiver
+     * @param targetVolume target volume to be set to AVR. It should be in range of [0-100]
+     * @param isVolumeUp whether to volume up or not. {@code true} for volume up; {@code false} for
+     *            volume down
+     * @return newly created {@link VolumeControlAction}
+     */
+    public static VolumeControlAction ofVolumeChange(HdmiCecLocalDevice source, int avrAddress,
+            int targetVolume, boolean isVolumeUp) {
+        Preconditions.checkArgumentInRange(targetVolume, MIN_VOLUME, MAX_VOLUME, "volume");
+        return new VolumeControlAction(source, avrAddress, targetVolume, isVolumeUp);
+    }
+
+    /**
+     * Scale a custom volume value to cec volume scale.
+     *
+     * @param volume volume value in custom scale
+     * @param scale scale of volume (max volume)
+     * @return a volume scaled to cec volume range
+     */
+    public static int scaleToCecVolume(int volume, int scale) {
+        return (volume * MAX_VOLUME) / scale;
+    }
+
+    /**
+     * Scale a cec volume which is in range of 0 to 100 to custom volume level.
+     *
+     * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
+     * @param scale scale of custom volume (max volume)
+     * @return a volume value scaled to custom volume range
+     */
+    public static int scaleToCustomVolume(int cecVolume, int scale) {
+        return (cecVolume * scale) / MAX_VOLUME;
+    }
+
+    private VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, int targetVolume,
+            boolean isVolumeUp) {
+        super(source);
+
+        mAvrAddress = avrAddress;
+        mTargetVolume = targetVolume;
+        mIsVolumeUp = isVolumeUp;
+    }
+
+    @Override
+    boolean start() {
+        if (isForMute()) {
+            sendMuteChange(mTargetVolume == VOLUME_MUTE);
+            finish();
+            return true;
+        }
+
+        startVolumeChange();
+        return true;
+    }
+
+
+    private boolean isForMute() {
+        return mTargetVolume == VOLUME_MUTE || mTargetVolume == VOLUME_RESTORE;
+    }
+
+
+    private void startVolumeChange() {
+        mTimeoutCount = 0;
+        sendVolumeChange(mIsVolumeUp);
+        mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS;
+        addTimer(mState, IRT_MS);
+    }
+
+    private void sendVolumeChange(boolean up) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
+                up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
+                        : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
+    }
+
+    private void sendMuteChange(boolean mute) {
+        sendUserControlPressedAndReleased(mAvrAddress,
+                mute ? HdmiConstants.UI_COMMAND_MUTE_FUNCTION :
+                        HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION);
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                handleReportAudioStatus(cmd);
+                return true;
+            case HdmiCec.MESSAGE_FEATURE_ABORT:
+                // TODO: handle feature abort.
+                finish();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void handleReportAudioStatus(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        if (params.length != 1) {
+            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
+            return;
+        }
+
+        int volume = params[0] & 0x7F;
+        // Update volume with new value.
+        // Note that it will affect system volume change.
+        tv().setAudioStatus(false, volume);
+        if (mIsVolumeUp) {
+            if (mTargetVolume <= volume) {
+                finishWithVolumeChangeRelease();
+                return;
+            }
+        } else {
+            if (mTargetVolume >= volume) {
+                finishWithVolumeChangeRelease();
+                return;
+            }
+        }
+
+        // Clear action status and send another volume change command.
+        clear();
+        startVolumeChange();
+    }
+
+    private void finishWithVolumeChangeRelease() {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                getSourceAddress(), mAvrAddress));
+        finish();
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+            return;
+        }
+
+        // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume
+        // action.
+        if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) {
+            finishWithVolumeChangeRelease();
+            return;
+        }
+
+        sendVolumeChange(mIsVolumeUp);
+        addTimer(mState, IRT_MS);
+    }
+}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 0e9a9cc..cab2728 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -22,6 +22,7 @@
 import java.util.Iterator;
 import java.util.List;
 
+import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
@@ -31,8 +32,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
 import android.os.Handler;
@@ -616,10 +617,13 @@
         // job that runs one of the app's services, as well as verifying that the
         // named service properly requires the BIND_JOB_SERVICE permission
         private void enforceValidJobRequest(int uid, JobInfo job) {
-            final PackageManager pm = getContext().getPackageManager();
+            final IPackageManager pm = AppGlobals.getPackageManager();
             final ComponentName service = job.getService();
             try {
-                ServiceInfo si = pm.getServiceInfo(service, 0);
+                ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
+                if (si == null) {
+                    throw new IllegalArgumentException("No such service " + service);
+                }
                 if (si.applicationInfo.uid != uid) {
                     throw new IllegalArgumentException("uid " + uid +
                             " cannot schedule job in " + service.getPackageName());
@@ -628,8 +632,8 @@
                     throw new IllegalArgumentException("Scheduled service " + service
                             + " does not require android.permission.BIND_JOB_SERVICE permission");
                 }
-            } catch (NameNotFoundException e) {
-                throw new IllegalArgumentException("No such service: " + service);
+            } catch (RemoteException e) {
+                // Can't happen; the Package Manager is in this same process
             }
         }
 
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 4aef2d31..538a252 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -19,19 +19,16 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
-import android.os.BatteryProperty;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.BatteryManagerInternal;
 import android.os.SystemClock;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.BatteryService;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
@@ -158,14 +155,10 @@
             mContext.registerReceiver(this, filter);
 
             // Initialise tracker state.
-            BatteryService batteryService = (BatteryService) ServiceManager.getService("battery");
-            if (batteryService != null) {
-                mBatteryHealthy = !batteryService.getBatteryLevelLow();
-                mCharging = batteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-            } else {
-                // Unavailable for some reason, we default to false and let ACTION_BATTERY_[OK,LOW]
-                // sort it out.
-            }
+            BatteryManagerInternal batteryManagerInternal =
+                    LocalServices.getService(BatteryManagerInternal.class);
+            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
+            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
         }
 
         boolean isOnStablePower() {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 15a6b25..53337c4 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -203,7 +203,8 @@
     @Override
     public String toString() {
         return String.valueOf(hashCode()).substring(0, 3) + ".."
-                + ":[" + job.getService().getPackageName() + ",jId=" + job.getId()
+                + ":[" + job.getService()
+                + ",jId=" + job.getId()
                 + ",R=(" + earliestRunTimeElapsedMillis + "," + latestRunTimeElapsedMillis + ")"
                 + ",N=" + job.getNetworkCapabilities() + ",C=" + job.isRequireCharging()
                 + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
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/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9ae8aed..aeade50 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,16 +16,15 @@
 
 package com.android.server.media;
 
-import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.media.routeprovider.RouteRequest;
 import android.media.session.ISessionController;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.MediaController;
-import android.media.session.RemoteVolumeProvider;
 import android.media.session.RouteCommand;
 import android.media.session.RouteInfo;
 import android.media.session.RouteOptions;
@@ -34,10 +33,14 @@
 import android.media.session.MediaSessionInfo;
 import android.media.session.RouteInterface;
 import android.media.session.PlaybackState;
+import android.media.session.ParcelableVolumeInfo;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.media.VolumeProvider;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -45,7 +48,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -63,6 +65,7 @@
  */
 public class MediaSessionRecord implements IBinder.DeathRecipient {
     private static final String TAG = "MediaSessionRecord";
+    private static final boolean DEBUG = false;
 
     /**
      * These are the playback states that count as currently active.
@@ -82,6 +85,12 @@
      */
     private static final int ACTIVE_BUFFER = 30000;
 
+    /**
+     * The amount of time we'll send an assumed volume after the last volume
+     * command before reverting to the last reported volume.
+     */
+    private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
+
     private final MessageHandler mHandler;
 
     private final int mOwnerPid;
@@ -105,6 +114,7 @@
     // TODO define a RouteState class with relevant info
     private int mRouteState;
     private long mFlags;
+    private ComponentName mMediaButtonReceiver;
 
     // TransportPerformer fields
 
@@ -115,11 +125,13 @@
     // End TransportPerformer fields
 
     // Volume handling fields
-    private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
+    private AudioManager mAudioManager;
+    private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL;
     private int mAudioStream = AudioManager.STREAM_MUSIC;
-    private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+    private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
+    private int mOptimisticVolume = -1;
     // End volume handling fields
 
     private boolean mIsActive = false;
@@ -138,6 +150,7 @@
         mSessionCb = new SessionCb(cb);
         mService = service;
         mHandler = new MessageHandler(handler.getLooper());
+        mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
     }
 
     /**
@@ -185,6 +198,10 @@
         return mSessionInfo;
     }
 
+    public ComponentName getMediaButtonReceiver() {
+        return mMediaButtonReceiver;
+    }
+
     /**
      * Get this session's flags.
      *
@@ -263,20 +280,73 @@
      *
      * @param delta The amount to adjust the volume by.
      */
-    public void adjustVolumeBy(int delta) {
-        if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
-            // Nothing to do, the volume cannot be changed
-            return;
+    public void adjustVolumeBy(int delta, int flags) {
+        if (isPlaybackActive(false)) {
+            flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
-        mSessionCb.adjustVolumeBy(delta);
+        if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
+            if (delta == 0) {
+                mAudioManager.adjustStreamVolume(mAudioStream, delta, flags);
+            } else {
+                int direction = 0;
+                int steps = delta;
+                if (delta > 0) {
+                    direction = 1;
+                } else if (delta < 0) {
+                    direction = -1;
+                    steps = -delta;
+                }
+                for (int i = 0; i < steps; i++) {
+                    mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
+                }
+            }
+        } else {
+            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+                // Nothing to do, the volume cannot be changed
+                return;
+            }
+            mSessionCb.adjustVolumeBy(delta);
+
+            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
+            mOptimisticVolume = volumeBefore + delta;
+            mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
+            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
+            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
+            if (volumeBefore != mOptimisticVolume) {
+                pushVolumeUpdate();
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
+                        + mMaxVolume);
+            }
+        }
     }
 
-    public void setVolumeTo(int value) {
-        if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
-            // Nothing to do. The volume can't be set directly.
-            return;
+    public void setVolumeTo(int value, int flags) {
+        if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
+            mAudioManager.setStreamVolume(mAudioStream, value, flags);
+        } else {
+            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+                // Nothing to do. The volume can't be set directly.
+                return;
+            }
+            value = Math.max(0, Math.min(value, mMaxVolume));
+            mSessionCb.setVolumeTo(value);
+
+            int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
+            mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
+            mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
+            mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
+            if (volumeBefore != mOptimisticVolume) {
+                pushVolumeUpdate();
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
+                        + mMaxVolume);
+            }
         }
-        mSessionCb.setVolumeTo(value);
     }
 
     /**
@@ -350,7 +420,7 @@
      * @return The current type of playback.
      */
     public int getPlaybackType() {
-        return mPlaybackType;
+        return mVolumeType;
     }
 
     /**
@@ -393,6 +463,16 @@
     }
 
     /**
+     * Get the volume we'd like it to be set to. This is only valid for a short
+     * while after a call to adjust or set volume.
+     *
+     * @return The current optimistic volume or -1.
+     */
+    public int getOptimisticVolume() {
+        return mOptimisticVolume;
+    }
+
+    /**
      * @return True if this session is currently connected to a route.
      */
     public boolean isConnected() {
@@ -506,9 +586,11 @@
                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onPlaybackStateChanged(mPlaybackState);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e);
+                } catch (DeadObjectException e) {
                     mControllerCallbacks.remove(i);
+                    Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
                 }
             }
         }
@@ -523,9 +605,30 @@
                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onMetadataChanged(mMetadata);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e);
+                } catch (DeadObjectException e) {
+                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
                     mControllerCallbacks.remove(i);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
+                }
+            }
+        }
+    }
+
+    private void pushVolumeUpdate() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            ParcelableVolumeInfo info = mController.getVolumeAttributes();
+            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
+                try {
+                    cb.onVolumeInfoChanged(info);
+                } catch (DeadObjectException e) {
+                    Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
                 }
             }
         }
@@ -540,9 +643,11 @@
                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onRouteChanged(mRoute);
-                } catch (RemoteException e) {
+                } catch (DeadObjectException e) {
                     Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
                     mControllerCallbacks.remove(i);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushRouteUpdate.", e);
                 }
             }
         }
@@ -557,8 +662,11 @@
                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
                 try {
                     cb.onEvent(event, data);
+                } catch (DeadObjectException e) {
+                    Log.w(TAG, "Removing dead callback in pushEvent.", e);
+                    mControllerCallbacks.remove(i);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Error with callback in pushEvent.", e);
+                    Log.w(TAG, "unexpected exception in pushEvent.", e);
                 }
             }
         }
@@ -611,6 +719,16 @@
         return result == null ? state : result;
     }
 
+    private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
+        IBinder binder = cb.asBinder();
+        for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+            if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     private final RouteConnectionRecord.Listener mConnectionListener
             = new RouteConnectionRecord.Listener() {
         @Override
@@ -626,6 +744,17 @@
         }
     };
 
+    private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
+            mOptimisticVolume = -1;
+            if (needUpdate) {
+                pushVolumeUpdate();
+            }
+        }
+    };
+
     private final class SessionStub extends ISession.Stub {
         @Override
         public void destroy() {
@@ -661,6 +790,11 @@
         }
 
         @Override
+        public void setMediaButtonReceiver(ComponentName mbr) {
+            mMediaButtonReceiver = mbr;
+        }
+
+        @Override
         public void setMetadata(MediaMetadata metadata) {
             mMetadata = metadata;
             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
@@ -726,13 +860,15 @@
         @Override
         public void setCurrentVolume(int volume) {
             mCurrentVolume = volume;
+            mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
         }
 
         @Override
         public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
+            boolean typeChanged = type != mVolumeType;
             switch(type) {
-                case MediaSession.VOLUME_TYPE_LOCAL:
-                    mPlaybackType = type;
+                case MediaSession.PLAYBACK_TYPE_LOCAL:
+                    mVolumeType = type;
                     int audioStream = arg1;
                     if (isValidStream(audioStream)) {
                         mAudioStream = audioStream;
@@ -741,8 +877,8 @@
                         mAudioStream = AudioManager.STREAM_MUSIC;
                     }
                     break;
-                case MediaSession.VOLUME_TYPE_REMOTE:
-                    mPlaybackType = type;
+                case MediaSession.PLAYBACK_TYPE_REMOTE:
+                    mVolumeType = type;
                     mVolumeControlType = arg1;
                     mMaxVolume = arg2;
                     break;
@@ -750,6 +886,9 @@
                     throw new IllegalArgumentException("Volume handling type " + type
                             + " not recognized.");
             }
+            if (typeChanged) {
+                mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+            }
         }
 
         private boolean isValidStream(int stream) {
@@ -929,8 +1068,11 @@
         @Override
         public void registerCallbackListener(ISessionControllerCallback cb) {
             synchronized (mLock) {
-                if (!mControllerCallbacks.contains(cb)) {
+                if (getControllerCbIndexForCb(cb) < 0) {
                     mControllerCallbacks.add(cb);
+                    if (DEBUG) {
+                        Log.d(TAG, "registering controller callback " + cb);
+                    }
                 }
             }
         }
@@ -939,7 +1081,13 @@
         public void unregisterCallbackListener(ISessionControllerCallback cb)
                 throws RemoteException {
             synchronized (mLock) {
-                mControllerCallbacks.remove(cb);
+                int index = getControllerCbIndexForCb(cb);
+                if (index != -1) {
+                    mControllerCallbacks.remove(index);
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
+                }
             }
         }
 
@@ -954,6 +1102,46 @@
         }
 
         @Override
+        public ParcelableVolumeInfo getVolumeAttributes() {
+            synchronized (mLock) {
+                int type;
+                int max;
+                int current;
+                if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) {
+                    type = mVolumeControlType;
+                    max = mMaxVolume;
+                    current = mOptimisticVolume != -1 ? mOptimisticVolume
+                            : mCurrentVolume;
+                } else {
+                    type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+                    max = mAudioManager.getStreamMaxVolume(mAudioStream);
+                    current = mAudioManager.getStreamVolume(mAudioStream);
+                }
+                return new ParcelableVolumeInfo(mVolumeType, mAudioStream, type, max, current);
+            }
+        }
+
+        @Override
+        public void adjustVolumeBy(int delta, int flags) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                MediaSessionRecord.this.setVolumeTo(value, flags);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void play() throws RemoteException {
             mSessionCb.play();
         }
@@ -1033,6 +1221,7 @@
         private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
         private static final int MSG_SEND_COMMAND = 6;
         private static final int MSG_UPDATE_SESSION_STATE = 7;
+        private static final int MSG_UPDATE_VOLUME = 8;
 
         public MessageHandler(Looper looper) {
             super(looper);
@@ -1060,6 +1249,9 @@
                 case MSG_UPDATE_SESSION_STATE:
                     // TODO add session state
                     break;
+                case MSG_UPDATE_VOLUME:
+                    pushVolumeUpdate();
+                    break;
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 67065ba..fe68a86 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -23,11 +23,12 @@
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.media.AudioManager;
 import android.media.IAudioService;
+import android.media.IRemoteVolumeController;
 import android.media.routeprovider.RouteRequest;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ISession;
@@ -88,6 +89,7 @@
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
+    private ContentResolver mContentResolver;
 
     private MediaSessionRecord mPrioritySession;
     private int mCurrentUserId = -1;
@@ -96,8 +98,9 @@
     // session so we drop late callbacks properly.
     private int mShowRoutesRequestId = 0;
 
-    // TODO refactor to have per user state for providers. See
-    // MediaRouterService for an example
+    // Used to notify system UI when remote volume was changed. TODO find a
+    // better way to handle this.
+    private IRemoteVolumeController mRvc;
 
     public MediaSessionService(Context context) {
         super(context);
@@ -115,6 +118,7 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
+        mContentResolver = getContext().getContentResolver();
     }
 
     private IAudioService getAudioService() {
@@ -222,6 +226,16 @@
         }
     }
 
+    public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
+        synchronized (mLock) {
+            if (!mAllSessions.contains(record)) {
+                Log.d(TAG, "Unknown session changed playback type. Ignoring.");
+                return;
+            }
+            pushRemoteVolumeUpdateLocked(record.getUserId());
+        }
+    }
+
     @Override
     public void onStartUser(int userHandle) {
         updateUser();
@@ -364,6 +378,13 @@
         }
     }
 
+    private void enforceStatusBarPermission(String action, int pid, int uid) {
+        if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                pid, uid) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Only system ui may " + action);
+        }
+    }
+
     /**
      * This checks if the component is an enabled notification listener for the
      * specified user. Enabled components may only operate on behalf of the user
@@ -380,9 +401,11 @@
             // You may not access another user's content as an enabled listener.
             return false;
         }
+        if (DEBUG) {
+            Log.d(TAG, "Checking if enabled notification listener " + compName);
+        }
         if (compName != null) {
-            final String enabledNotifListeners = Settings.Secure.getStringForUser(
-                    getContext().getContentResolver(),
+            final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                     userId);
             if (enabledNotifListeners != null) {
@@ -485,10 +508,14 @@
         synchronized (mLock) {
             List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
             int size = records.size();
+            if (size > 0) {
+                persistMediaButtonReceiverLocked(records.get(0));
+            }
             ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
             }
+            pushRemoteVolumeUpdateLocked(userId);
             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
                 SessionsListenerRecord record = mSessionsListeners.get(i);
                 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
@@ -504,6 +531,27 @@
         }
     }
 
+    private void pushRemoteVolumeUpdateLocked(int userId) {
+        if (mRvc != null) {
+            try {
+                MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
+                mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
+            }
+        }
+    }
+
+    private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
+        ComponentName receiver = record.getMediaButtonReceiver();
+        if (receiver != null) {
+            Settings.System.putStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER,
+                    receiver == null ? "" : receiver.flattenToString(),
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
     private MediaRouteProviderProxy.RoutesListener mRoutesCallback
             = new MediaRouteProviderProxy.RoutesListener() {
         @Override
@@ -826,6 +874,19 @@
         }
 
         @Override
+        public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                enforceStatusBarPermission("listen for volume changes", pid, uid);
+                mRvc = rvc;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -881,14 +942,6 @@
 
         private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
                 MediaSessionRecord session) {
-            int direction = 0;
-            int steps = delta;
-            if (delta > 0) {
-                direction = 1;
-            } else if (delta < 0) {
-                direction = -1;
-                steps = -delta;
-            }
             if (DEBUG) {
                 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
                 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
@@ -901,6 +954,14 @@
                         mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
                                 getContext().getOpPackageName());
                     } else {
+                        int direction = 0;
+                        int steps = delta;
+                        if (delta > 0) {
+                            direction = 1;
+                        } else if (delta < 0) {
+                            direction = -1;
+                            steps = -delta;
+                        }
                         for (int i = 0; i < steps; i++) {
                             mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
                                     flags, getContext().getOpPackageName());
@@ -910,25 +971,13 @@
                     Log.e(TAG, "Error adjusting default volume.", e);
                 }
             } else {
-                if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
+                session.adjustVolumeBy(delta, flags);
+                if (mRvc != null) {
                     try {
-                        if (delta == 0) {
-                            mAudioService.adjustSuggestedStreamVolume(delta,
-                                    session.getAudioStream(), flags,
-                                    getContext().getOpPackageName());
-                        } else {
-                            for (int i = 0; i < steps; i++) {
-                                mAudioService.adjustSuggestedStreamVolume(direction,
-                                        session.getAudioStream(), flags,
-                                        getContext().getOpPackageName());
-                            }
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error adjusting volume for stream "
-                                + session.getAudioStream(), e);
+                        mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+                    } catch (Exception e) {
+                        Log.wtf(TAG, "Error sending volume change to system UI.", e);
                     }
-                } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
-                    session.adjustVolumeBy(delta);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 144ccfa..e26a2eb 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -203,6 +203,19 @@
         return null;
     }
 
+    public MediaSessionRecord getDefaultRemoteSession(int userId) {
+        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
+
+        int size = records.size();
+        for (int i = 0; i < size; i++) {
+            MediaSessionRecord record = records.get(i);
+            if (record.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE) {
+                return record;
+            }
+        }
+        return null;
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
                 UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 15f0ebf..f8ac2e4 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -36,6 +36,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.notification.NotificationManagerService.DumpFilter;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -74,16 +75,19 @@
     }
 
     @Override
-    public void dump(PrintWriter pw) {
-        super.dump(pw);
+    public void dump(PrintWriter pw, DumpFilter filter) {
+        super.dump(pw, filter);
         synchronized(mMutex) {
-            pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
-            for (int i = 0; i < mListeners.size(); i++) {
-                pw.print("      "); pw.println(mListeners.keyAt(i));
+            if (filter == null) {
+                pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
+                for (int i = 0; i < mListeners.size(); i++) {
+                    pw.print("      "); pw.println(mListeners.keyAt(i));
+                }
             }
             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
             for (int i = 0; i < mRecords.size(); i++) {
                 final ConditionRecord r = mRecords.get(i);
+                if (filter != null && !filter.matches(r.component)) continue;
                 pw.print("      "); pw.println(r);
                 final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
                 if (countdownDesc != null) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 1b1fc8b..36be21f 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -44,6 +44,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -116,15 +118,17 @@
         mSettingsObserver.observe();
     }
 
-    public void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw, DumpFilter filter) {
         pw.println("    All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
                 + ") enabled for current profiles:");
         for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+            if (filter != null && !filter.matches(cmpt)) continue;
             pw.println("      " + cmpt);
         }
 
         pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
         for (ManagedServiceInfo info : mServices) {
+            if (filter != null && !filter.matches(info.component)) continue;
             pw.println("      " + info.component
                     + " (user " + info.userid + "): " + info.service
                     + (info.isSystem?" SYSTEM":""));
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 05aa2c7..bc14888 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();
@@ -439,7 +440,8 @@
             this.duration = duration;
         }
 
-        void dump(PrintWriter pw, String prefix) {
+        void dump(PrintWriter pw, String prefix, DumpFilter filter) {
+            if (filter != null && !filter.matches(pkg)) return;
             pw.println(prefix + this);
         }
 
@@ -854,6 +856,9 @@
             }
         }
 
+        mArchive = new Archive(resources.getInteger(
+                R.integer.config_notificationServiceArchiveSize));
+
         publishBinderService(Context.NOTIFICATION_SERVICE, mService);
         publishLocalService(NotificationManagerInternal.class, mInternalService);
     }
@@ -1309,7 +1314,7 @@
                 return;
             }
 
-            dumpImpl(pw);
+            dumpImpl(pw, DumpFilter.parseFromArguments(args));
         }
     };
 
@@ -1330,9 +1335,12 @@
         return keys.toArray(new String[keys.size()]);
     }
 
-    void dumpImpl(PrintWriter pw) {
-        pw.println("Current Notification Manager state:");
-
+    void dumpImpl(PrintWriter pw, DumpFilter filter) {
+        pw.print("Current Notification Manager state");
+        if (filter != null) {
+            pw.print(" (filtered to '"); pw.print(filter.pkgFilter); pw.print("')");
+        }
+        pw.println(':');
         int N;
 
         synchronized (mToastQueue) {
@@ -1340,11 +1348,10 @@
             if (N > 0) {
                 pw.println("  Toast Queue:");
                 for (int i=0; i<N; i++) {
-                    mToastQueue.get(i).dump(pw, "    ");
+                    mToastQueue.get(i).dump(pw, "    ", filter);
                 }
                 pw.println("  ");
             }
-
         }
 
         synchronized (mNotificationList) {
@@ -1352,29 +1359,35 @@
             if (N > 0) {
                 pw.println("  Notification List:");
                 for (int i=0; i<N; i++) {
-                    mNotificationList.get(i).dump(pw, "    ", getContext());
+                    final NotificationRecord nr = mNotificationList.get(i);
+                    if (filter != null && !filter.matches(nr.sbn)) continue;
+                    nr.dump(pw, "    ", getContext());
                 }
                 pw.println("  ");
             }
 
-            N = mLights.size();
-            if (N > 0) {
-                pw.println("  Lights List:");
-                for (int i=0; i<N; i++) {
-                    pw.println("    " + mLights.get(i));
+            if (filter == null) {
+                N = mLights.size();
+                if (N > 0) {
+                    pw.println("  Lights List:");
+                    for (int i=0; i<N; i++) {
+                        pw.println("    " + mLights.get(i));
+                    }
+                    pw.println("  ");
                 }
-                pw.println("  ");
-            }
 
-            pw.println("  mSoundNotification=" + mSoundNotification);
-            pw.println("  mVibrateNotification=" + mVibrateNotification);
-            pw.println("  mDisableNotificationAlerts=" + mDisableNotificationAlerts);
-            pw.println("  mSystemReady=" + mSystemReady);
+                pw.println("  mSoundNotification=" + mSoundNotification);
+                pw.println("  mVibrateNotification=" + mVibrateNotification);
+                pw.println("  mDisableNotificationAlerts=" + mDisableNotificationAlerts);
+                pw.println("  mSystemReady=" + mSystemReady);
+            }
             pw.println("  mArchive=" + mArchive.toString());
             Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
             int i=0;
             while (iter.hasNext()) {
-                pw.println("    " + iter.next());
+                final StatusBarNotification sbn = iter.next();
+                if (filter != null && !filter.matches(sbn)) continue;
+                pw.println("    " + sbn);
                 if (++i >= 5) {
                     if (iter.hasNext()) pw.println("    ...");
                     break;
@@ -1382,16 +1395,18 @@
             }
 
             pw.println("\n  Usage Stats:");
-            mUsageStats.dump(pw, "    ");
+            mUsageStats.dump(pw, "    ", filter);
 
-            pw.println("\n  Zen Mode:");
-            mZenModeHelper.dump(pw, "    ");
+            if (filter == null) {
+                pw.println("\n  Zen Mode:");
+                mZenModeHelper.dump(pw, "    ");
+            }
 
             pw.println("\n  Notification listeners:");
-            mListeners.dump(pw);
+            mListeners.dump(pw, filter);
 
             pw.println("\n  Condition providers:");
-            mConditionProviders.dump(pw);
+            mConditionProviders.dump(pw, filter);
         }
     }
 
@@ -1542,41 +1557,11 @@
 
                     Collections.sort(mNotificationList, mRankingComparator);
 
-                    final int currentUser;
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        currentUser = ActivityManager.getCurrentUser();
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-
                     if (notification.icon != 0) {
-                        if (old != null && !old.isCanceled) {
-                            final long identity = Binder.clearCallingIdentity();
-                            try {
-                                mStatusBar.updateNotification(n);
-                            } finally {
-                                Binder.restoreCallingIdentity(identity);
-                            }
-                        } else {
-                            final long identity = Binder.clearCallingIdentity();
-                            try {
-                                mStatusBar.addNotification(n);
-                            } finally {
-                                Binder.restoreCallingIdentity(identity);
-                            }
-                        }
                         mListeners.notifyPostedLocked(n);
                     } else {
                         Slog.e(TAG, "Not posting notification with icon==0: " + notification);
                         if (old != null && !old.isCanceled) {
-                            final long identity = Binder.clearCallingIdentity();
-                            try {
-                                mStatusBar.removeNotification(r.getKey());
-                            } finally {
-                                Binder.restoreCallingIdentity(identity);
-                            }
-
                             mListeners.notifyRemovedLocked(n);
                         }
                         // ATTENTION: in a future release we will bail out here
@@ -1988,12 +1973,6 @@
 
         // status bar
         if (r.getNotification().icon != 0) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                mStatusBar.removeNotification(r.getKey());
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
             r.isCanceled = true;
             mListeners.notifyRemovedLocked(r.sbn);
         }
@@ -2479,4 +2458,30 @@
             }
         }
     }
+
+    public static final class DumpFilter {
+        public String pkgFilter;
+
+        public static DumpFilter parseFromArguments(String[] args) {
+            if (args == null || args.length != 2 || !"p".equals(args[0])
+                    || args[1] == null || args[1].trim().isEmpty()) {
+                return null;
+            }
+            final DumpFilter filter = new DumpFilter();
+            filter.pkgFilter = args[1].trim().toLowerCase();
+            return filter;
+        }
+
+        public boolean matches(StatusBarNotification sbn) {
+            return sbn != null && (matches(sbn.getPackageName()) || matches(sbn.getOpPkg()));
+        }
+
+        public boolean matches(ComponentName component) {
+            return component != null && matches(component.getPackageName());
+        }
+
+        public boolean matches(String pkg) {
+            return pkg != null && pkg.toLowerCase().contains(pkgFilter);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index bab4895..adf9516 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -28,6 +28,8 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Map;
@@ -44,7 +46,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>();
@@ -168,12 +170,13 @@
         return result;
     }
 
-    public synchronized void dump(PrintWriter pw, String indent) {
+    public synchronized void dump(PrintWriter pw, String indent, DumpFilter filter) {
         for (AggregatedStats as : mStats.values()) {
+            if (filter != null && !filter.matches(as.key)) continue;
             as.dump(pw, indent);
         }
         if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.dump(pw, indent);
+            mSQLiteLog.dump(pw, indent, filter);
         }
     }
 
@@ -511,7 +514,7 @@
             mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
         }
 
-        public void printPostFrequencies(PrintWriter pw, String indent) {
+        public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
             SQLiteDatabase db = mHelper.getReadableDatabase();
             long nowMs = System.currentTimeMillis();
             String q = "SELECT " +
@@ -530,6 +533,7 @@
                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                     int userId = cursor.getInt(0);
                     String pkg = cursor.getString(1);
+                    if (filter != null && !filter.matches(pkg)) continue;
                     int day = cursor.getInt(2);
                     int count = cursor.getInt(3);
                     pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg +
@@ -598,8 +602,8 @@
             outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
         }
 
-        public void dump(PrintWriter pw, String indent) {
-            printPostFrequencies(pw, indent);
+        public void dump(PrintWriter pw, String indent, DumpFilter filter) {
+            printPostFrequencies(pw, indent, filter);
         }
     }
 }
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/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index f2db791..1d53016 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -16,12 +16,13 @@
 
 package com.android.server.pm;
 
-import android.content.BroadcastReceiver;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.util.Log;
 
 import java.util.HashSet;
@@ -30,62 +31,63 @@
 /**
  * {@hide}
  */
-public class BackgroundDexOptService {
-
+public class BackgroundDexOptService extends JobService {
     static final String TAG = "BackgroundDexOptService";
 
-    private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
-                onIdleStart();
-            } else if (Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
-                onIdleStop();
-            }
-        }
-    };
-
-    final PackageManagerService mPackageManager;
+    static final int BACKGROUND_DEXOPT_JOB = 800;
+    private static ComponentName sDexoptServiceName = new ComponentName(
+            "android",
+            BackgroundDexOptService.class.getName());
 
     final AtomicBoolean mIdleTime = new AtomicBoolean(false);
 
-    public BackgroundDexOptService(Context context) {
-        mPackageManager = (PackageManagerService)ServiceManager.getService("package");
-
-        IntentFilter idleMaintenanceFilter = new IntentFilter();
-        idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
-        idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_END);
-        context.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
-                                       idleMaintenanceFilter, null, null);
+    public static void schedule(Context context) {
+        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        JobInfo job = new JobInfo.Builder(BACKGROUND_DEXOPT_JOB, sDexoptServiceName)
+                .setRequiresDeviceIdle(true)
+                .setRequiresCharging(true)
+                .build();
+        js.schedule(job);
     }
 
-    public boolean onIdleStart() {
+    @Override
+    public boolean onStartJob(JobParameters params) {
         Log.i(TAG, "onIdleStart");
-        if (mPackageManager.isStorageLow()) {
+        final PackageManagerService pm =
+                (PackageManagerService)ServiceManager.getService("package");
+
+        if (pm.isStorageLow()) {
             return false;
         }
-        final HashSet<String> pkgs = mPackageManager.getPackagesThatNeedDexOpt();
+        final HashSet<String> pkgs = pm.getPackagesThatNeedDexOpt();
         if (pkgs == null) {
             return false;
         }
+
+        final JobParameters jobParams = params;
         mIdleTime.set(true);
         new Thread("BackgroundDexOptService_DexOpter") {
             @Override
             public void run() {
                 for (String pkg : pkgs) {
                     if (!mIdleTime.get()) {
-                        break;
+                        // stopped while still working, so we need to reschedule
+                        schedule(BackgroundDexOptService.this);
+                        return;
                     }
-                    mPackageManager.performDexOpt(pkg, false);
+                    pm.performDexOpt(pkg, false);
                 }
+                // ran to completion, so we abandon our timeslice and do not reschedule
+                jobFinished(jobParams, false);
             }
         }.start();
         return true;
     }
 
-    public void onIdleStop() {
+    @Override
+    public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "onIdleStop");
         mIdleTime.set(false);
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
new file mode 100644
index 0000000..3ce19c1f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import android.content.IntentFilter;
+import android.util.Log;
+import java.io.IOException;
+import android.os.UserHandle;
+
+/**
+ * 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 CrossProfileIntentFilter extends IntentFilter {
+    private static final String ATTR_TARGET_USER_ID = "targetUserId";
+    private static final String ATTR_FLAGS = "flags";
+    private static final String ATTR_FILTER = "filter";
+
+    private static final String TAG = "CrossProfileIntentFilter";
+
+    // If the intent matches the IntentFilter, then it can be forwarded to this userId.
+    final int mTargetUserId;
+    final int mFlags;
+
+    CrossProfileIntentFilter(IntentFilter filter, int targetUserId, int flags) {
+        super(filter);
+        mTargetUserId = targetUserId;
+        mFlags = flags;
+    }
+
+    public int getTargetUserId() {
+        return mTargetUserId;
+    }
+
+    public int getFlags() {
+        return mFlags;
+    }
+
+    CrossProfileIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+        String targetUserIdString = parser.getAttributeValue(null, ATTR_TARGET_USER_ID);
+        if (targetUserIdString == null) {
+            String msg = "Missing element under " + TAG +": " + ATTR_TARGET_USER_ID + " at " +
+                    parser.getPositionDescription();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+            mTargetUserId = UserHandle.USER_NULL;
+        } else {
+            mTargetUserId = Integer.parseInt(targetUserIdString);
+        }
+        String flagsString = parser.getAttributeValue(null, ATTR_FLAGS);
+        if (flagsString == null) {
+            String msg = "Missing element under " + TAG +": " + ATTR_FLAGS + " at " +
+                    parser.getPositionDescription();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+            mFlags = 0;
+        } else {
+            mFlags = Integer.parseInt(flagsString);
+        }
+        int outerDepth = parser.getDepth();
+        String tagName = parser.getName();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            tagName = parser.getName();
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            } else if (type == XmlPullParser.START_TAG) {
+                if (tagName.equals(ATTR_FILTER)) {
+                    break;
+                } else {
+                    String msg = "Unknown element under " + Settings.TAG_FORWARDING_INTENT_FILTERS
+                            + ": " + tagName + " at " + parser.getPositionDescription();
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        }
+        if (tagName.equals(ATTR_FILTER)) {
+            readFromXml(parser);
+        } else {
+            String msg = "Missing element under " + TAG + ": " + ATTR_FILTER +
+                    " at " + parser.getPositionDescription();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void writeToXml(XmlSerializer serializer) throws IOException {
+        serializer.attribute(null, ATTR_TARGET_USER_ID, Integer.toString(mTargetUserId));
+        serializer.attribute(null, ATTR_FLAGS, Integer.toString(mFlags));
+        serializer.startTag(null, ATTR_FILTER);
+            super.writeToXml(serializer);
+        serializer.endTag(null, ATTR_FILTER);
+    }
+
+    @Override
+    public String toString() {
+        return "CrossProfileIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
+                + " " + Integer.toString(mTargetUserId) + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
new file mode 100644
index 0000000..a335d3a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+
+package com.android.server.pm;
+
+
+import java.io.PrintWriter;
+import com.android.server.IntentResolver;
+import java.util.List;
+
+/**
+ * Used to find a list of {@link CrossProfileIntentFilter}s that match an intent.
+ */
+class CrossProfileIntentResolver
+        extends IntentResolver<CrossProfileIntentFilter, CrossProfileIntentFilter> {
+    @Override
+    protected CrossProfileIntentFilter[] newArray(int size) {
+        return new CrossProfileIntentFilter[size];
+    }
+
+    @Override
+    protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) {
+        return false;
+    }
+
+    @Override
+    protected void sortResults(List<CrossProfileIntentFilter> results) {
+        //We don't sort the results
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java b/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
deleted file mode 100644
index 85bde98..0000000
--- a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.pm;
-
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-import android.content.IntentFilter;
-import android.util.Log;
-import java.io.IOException;
-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}.
- */
-class ForwardingIntentFilter extends IntentFilter {
-    private static final String ATTR_USER_ID_DEST = "userIdDest";
-    private static final String ATTR_REMOVABLE = "removable";
-    private static final String ATTR_FILTER = "filter";
-
-    private static final String TAG = "ForwardingIntentFilter";
-
-    // If the intent matches the IntentFilter, then it can be forwarded to this userId.
-    final int mUserIdDest;
-    boolean mRemovable;
-
-    ForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdDest) {
-        super(filter);
-        mUserIdDest = userIdDest;
-        mRemovable = removable;
-    }
-
-    public int getUserIdDest() {
-        return mUserIdDest;
-    }
-
-    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 " +
-                    parser.getPositionDescription();
-            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-            mUserIdDest = UserHandle.USER_NULL;
-        } else {
-            mUserIdDest = Integer.parseInt(userIdDestString);
-        }
-        String removableString = parser.getAttributeValue(null, ATTR_REMOVABLE);
-        if (removableString != null) {
-            mRemovable = Boolean.parseBoolean(removableString);
-        }
-        int outerDepth = parser.getDepth();
-        String tagName = parser.getName();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            tagName = parser.getName();
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            } else if (type == XmlPullParser.START_TAG) {
-                if (tagName.equals(ATTR_FILTER)) {
-                    break;
-                } else {
-                    String msg = "Unknown element under " + Settings.TAG_FORWARDING_INTENT_FILTERS
-                            + ": " + tagName + " at " + parser.getPositionDescription();
-                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-                    XmlUtils.skipCurrentTag(parser);
-                }
-            }
-        }
-        if (tagName.equals(ATTR_FILTER)) {
-            readFromXml(parser);
-        } else {
-            String msg = "Missing element under " + TAG + ": " + ATTR_FILTER +
-                    " at " + parser.getPositionDescription();
-            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-            XmlUtils.skipCurrentTag(parser);
-        }
-    }
-
-    public void writeToXml(XmlSerializer serializer) throws IOException {
-        serializer.attribute(null, ATTR_USER_ID_DEST, Integer.toString(mUserIdDest));
-        serializer.attribute(null, ATTR_REMOVABLE, Boolean.toString(mRemovable));
-        serializer.startTag(null, ATTR_FILTER);
-            super.writeToXml(serializer);
-        serializer.endTag(null, ATTR_FILTER);
-    }
-
-    @Override
-    public String toString() {
-        return "ForwardingIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
-                + " " + Integer.toString(mUserIdDest) + "}";
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java b/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
deleted file mode 100644
index 1616395..0000000
--- a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-
-package com.android.server.pm;
-
-
-import java.io.PrintWriter;
-import com.android.server.IntentResolver;
-import java.util.List;
-
-/**
- * Used to find a list of {@link ForwardingIntentFilter}s that match an intent.
- */
-class ForwardingIntentResolver
-        extends IntentResolver<ForwardingIntentFilter, ForwardingIntentFilter> {
-    @Override
-    protected ForwardingIntentFilter[] newArray(int size) {
-        return new ForwardingIntentFilter[size];
-    }
-
-    @Override
-    protected boolean isPackageForFilter(String packageName, ForwardingIntentFilter filter) {
-        return false;
-    }
-
-    @Override
-    protected void sortResults(List<ForwardingIntentFilter> results) {
-        //We don't sort the results
-    }
-}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 82d3f53..7189960 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -27,6 +27,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.List;
 
 public final class Installer extends SystemService {
     private static final String TAG = "Installer";
@@ -345,6 +346,10 @@
         }
     }
 
+    public int pruneDexCache(String cacheSubDir) {
+        return execute("prunedexcache " + cacheSubDir);
+    }
+
     public int freeCache(long freeStorageSize) {
         StringBuilder builder = new StringBuilder("freecache");
         builder.append(' ');
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5e3325c..65cb6c9 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,375 @@
  * 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,
+                        PackageManager.NO_CROSS_PROFILE, // We only want the apps for this user
+                        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..89ab2ae 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -28,10 +28,12 @@
 import android.content.pm.PackageInstallerParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.ApkLite;
+import android.content.pm.PackageParser.PackageParserException;
 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;
@@ -49,14 +51,10 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
-import libcore.io.IoUtils;
 import libcore.io.Libcore;
-import libcore.io.Streams;
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -114,7 +112,7 @@
     private boolean mPermissionsConfirmed;
     private boolean mInvalid;
 
-    private ArrayList<WritePipe> mPipes = new ArrayList<>();
+    private ArrayList<FileBridge> mBridges = new ArrayList<>();
 
     private IPackageInstallObserver2 mRemoteObserver;
 
@@ -159,14 +157,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 +192,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 +216,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");
                 }
@@ -296,11 +294,12 @@
 
         // Verify that all staged packages are internally consistent
         for (File file : files) {
-            final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
-                    PackageParser.PARSE_GET_SIGNATURES);
-            if (info == null) {
+            final ApkLite info;
+            try {
+                info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
+            } catch (PackageParserException e) {
                 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
-                        "Failed to parse " + file);
+                        "Failed to parse " + file + ": " + e);
             }
 
             if (!seenSplits.add(info.splitName)) {
@@ -355,11 +354,13 @@
                         "Missing existing base package for " + mPackageName);
             }
 
-            final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
-                    PackageParser.PARSE_GET_SIGNATURES);
-            if (info == null) {
+            final ApkLite info;
+            try {
+                info = PackageParser.parseApkLite(new File(app.sourceDir),
+                        PackageParser.PARSE_GET_SIGNATURES);
+            } catch (PackageParserException e) {
                 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
-                        "Failed to parse existing base " + app.sourceDir);
+                        "Failed to parse existing base " + app.sourceDir + ": " + e);
             }
 
             assertPackageConsistent("Existing base", info.packageName, info.versionCode,
@@ -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..cac27bc 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -40,7 +40,9 @@
 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.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
@@ -51,7 +53,6 @@
 import com.android.server.Watchdog;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.storage.DeviceStorageMonitorInternal;
-import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -89,9 +90,9 @@
 import android.content.pm.PackageCleanItem;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageInstallerParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageUserState;
@@ -135,6 +136,7 @@
 import android.system.Os;
 import android.system.StructStat;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -143,6 +145,7 @@
 import android.util.PrintStreamPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.Xml;
 import android.view.Display;
 
@@ -411,9 +414,6 @@
     final HashMap<String, SharedLibraryEntry> mSharedLibraries
             = new HashMap<String, SharedLibraryEntry>();
 
-    // Temporary for building the final shared libraries for an .apk.
-    String[] mTmpSharedLibraries = null;
-
     // These are the features this devices supports that were read from the
     // etc/permissions.xml file.
     final HashMap<String, FeatureInfo> mAvailableFeatures =
@@ -621,10 +621,10 @@
         private final AtomicLong mLastWritten = new AtomicLong(0);
         private final AtomicBoolean mBackgroundWriteRunning = new AtomicBoolean(false);
 
-        private boolean mIsFirstBoot = false;
+        private boolean mIsHistoricalPackageUsageAvailable = true;
 
-        boolean isFirstBoot() {
-            return mIsFirstBoot;
+        boolean isHistoricalPackageUsageAvailable() {
+            return mIsHistoricalPackageUsageAvailable;
         }
 
         void write(boolean force) {
@@ -715,7 +715,7 @@
                         pkg.mLastPackageUsageTimeInMills = timeInMillis;
                     }
                 } catch (FileNotFoundException expected) {
-                    mIsFirstBoot = true;
+                    mIsHistoricalPackageUsageAvailable = false;
                 } catch (IOException e) {
                     Log.w(TAG, "Failed to read package usage times", e);
                 } finally {
@@ -1247,7 +1247,7 @@
         }
     }
 
-    public static final IPackageManager main(Context context, Installer installer,
+    public static final PackageManagerService main(Context context, Installer installer,
             boolean factoryTest, boolean onlyCore) {
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
@@ -1484,7 +1484,26 @@
             }
 
             if (didDexOptLibraryOrTool) {
-                pruneDexFiles(new File(dataDir, "dalvik-cache"));
+                // If we dexopted a library or tool, then something on the system has
+                // changed. Consider this significant, and wipe away all other
+                // existing dexopt files to ensure we don't leave any dangling around.
+                //
+                // TODO: This should be revisited because it isn't as good an indicator
+                // as it used to be. It used to include the boot classpath but at some point
+                // DexFile.isDexOptNeeded started returning false for the boot
+                // class path files in all cases. It is very possible in a
+                // small maintenance release update that the library and tool
+                // jars may be unchanged but APK could be removed resulting in
+                // unused dalvik-cache files.
+                for (String instructionSet : instructionSets) {
+                    mInstaller.pruneDexCache(instructionSet);
+                }
+
+                // Additionally, delete all dex files from the root directory
+                // since there shouldn't be any there anyway, unless we're upgrading
+                // from an older OS version or a build that contained the "old" style
+                // flat scheme.
+                mInstaller.pruneDexCache(".");
             }
 
             // Collect vendor overlay packages.
@@ -1726,48 +1745,9 @@
         Runtime.getRuntime().gc();
     }
 
-    private static void pruneDexFiles(File cacheDir) {
-        // If we had to do a dexopt of one of the previous
-        // things, then something on the system has changed.
-        // Consider this significant, and wipe away all other
-        // existing dexopt files to ensure we don't leave any
-        // dangling around.
-        //
-        // Additionally, delete all dex files from the root directory
-        // since there shouldn't be any there anyway.
-        //
-        // Note: This isn't as good an indicator as it used to be. It
-        // used to include the boot classpath but at some point
-        // DexFile.isDexOptNeeded started returning false for the boot
-        // class path files in all cases. It is very possible in a
-        // small maintenance release update that the library and tool
-        // jars may be unchanged but APK could be removed resulting in
-        // unused dalvik-cache files.
-        File[] files = cacheDir.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                if (!file.isDirectory()) {
-                    Slog.i(TAG, "Pruning dalvik file: " + file.getAbsolutePath());
-                    file.delete();
-                } else {
-                    File[] subDirList = file.listFiles();
-                    if (subDirList != null) {
-                        for (File subDirFile : subDirList) {
-                            final String fn = subDirFile.getName();
-                            if (fn.startsWith("data@app@") || fn.startsWith("data@app-private@")) {
-                                Slog.i(TAG, "Pruning dalvik file: " + fn);
-                                subDirFile.delete();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
     @Override
     public boolean isFirstBoot() {
-        return !mRestoredSettings || mPackageUsage.isFirstBoot();
+        return !mRestoredSettings;
     }
 
     @Override
@@ -2267,12 +2247,12 @@
                 if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
                     return null;
                 }
-                // TODO: teach about reading split name
-                pkg = new PackageParser.Package(packageName, null);
+                // App code is gone, so we aren't worried about split paths
+                pkg = new PackageParser.Package(packageName);
                 pkg.applicationInfo.packageName = packageName;
                 pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
-                pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
                 pkg.applicationInfo.sourceDir = ps.codePathString;
+                pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
                 pkg.applicationInfo.dataDir =
                         getDataPathForPackage(packageName, 0).getPath();
                 pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
@@ -3371,28 +3351,49 @@
     }
 
     /*
-     * Returns if intent can be forwarded from the userId from to dest
+     * Returns if intent can be forwarded from the sourceUserId to the targetUserId
      */
     @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;
+            }
+        }
+
+        ArrayList<String> packageNames = null;
+        SparseArray<ArrayList<String>> fromSource =
+                mSettings.mCrossProfilePackageInfo.get(sourceUserId);
+        if (fromSource != null) {
+            packageNames = fromSource.get(targetUserId);
+        }
+        if (packageNames.contains(intent.getPackage())) {
+            return true;
+        }
+        // We need the package name, so we try to resolve with the loosest flags possible
+        List<ResolveInfo> resolveInfos = mActivities.queryIntent(
+                intent, resolvedType, PackageManager.GET_UNINSTALLED_PACKAGES, targetUserId);
+        int count = resolveInfos.size();
+        for (int i = 0; i < count; i++) {
+            ResolveInfo resolveInfo = resolveInfos.get(i);
+            if (packageNames.contains(resolveInfo.activityInfo.packageName)) {
+                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 resolver = mSettings.mCrossProfileIntentResolvers.get(userId);
+        if (resolver != null) {
+            return resolver.queryIntent(intent, resolvedType, false, userId);
         }
         return null;
     }
@@ -3424,42 +3425,51 @@
         // reader
         synchronized (mPackages) {
             final String pkgName = intent.getPackage();
+            boolean queryCrossProfile = (flags & PackageManager.NO_CROSS_PROFILE) == 0;
             if (pkgName == null) {
-                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;
-                    HashSet<Integer> alreadyTriedUserIds = new HashSet<Integer>();
-                    for (ForwardingIntentFilter fif : fifs) {
-                        int userIdDest = fif.getUserIdDest();
-                        // Two {@link ForwardingIntentFilter}s can have the same userIdDest 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)) {
-                            List<ResolveInfo> resultUser = mActivities.queryIntent(intent,
-                                    resolvedType, flags, userIdDest);
-                            if (resultUser != null) {
-                                forwardingIntentFilterWithResult = fif;
-                                // As soon as there is a match in another user, we add the
-                                // intentForwarderActivity to the list of ResolveInfo.
-                                break;
-                            }
-                            alreadyTriedUserIds.add(userIdDest);
-                        }
+                ResolveInfo resolveInfo = null;
+                if (queryCrossProfile) {
+                    // Check if the intent needs to be forwarded to another user for this package
+                    ArrayList<ResolveInfo> crossProfileResult =
+                            queryIntentActivitiesCrossProfilePackage(
+                                    intent, resolvedType, flags, userId);
+                    if (!crossProfileResult.isEmpty()) {
+                        // Skip the current profile
+                        return crossProfileResult;
                     }
-                    if (forwardingIntentFilterWithResult != null) {
-                        ResolveInfo forwardingResolveInfo = createForwardingResolveInfo(
-                                forwardingIntentFilterWithResult, userId);
-                        result.add(forwardingResolveInfo);
+                    List<CrossProfileIntentFilter> matchingFilters =
+                            getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
+                    // Check for results that need to skip the current profile.
+                    resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent,
+                            resolvedType, flags, userId);
+                    if (resolveInfo != null) {
+                        List<ResolveInfo> result = new ArrayList<ResolveInfo>(1);
+                        result.add(resolveInfo);
+                        return result;
                     }
+                    // Check for cross profile results.
+                    resolveInfo = queryCrossProfileIntents(
+                            matchingFilters, intent, resolvedType, flags, userId);
+                }
+                // Check for results in the current profile.
+                List<ResolveInfo> result = mActivities.queryIntent(
+                        intent, resolvedType, flags, userId);
+                if (resolveInfo != null) {
+                    result.add(resolveInfo);
                 }
                 return result;
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
             if (pkg != null) {
+                if (queryCrossProfile) {
+                    ArrayList<ResolveInfo> crossProfileResult =
+                            queryIntentActivitiesCrossProfilePackage(
+                                    intent, resolvedType, flags, userId, pkg, pkgName);
+                    if (!crossProfileResult.isEmpty()) {
+                        // Skip the current profile
+                        return crossProfileResult;
+                    }
+                }
                 return mActivities.queryIntentForPackage(intent, resolvedType, flags,
                         pkg.activities, userId);
             }
@@ -3467,25 +3477,135 @@
         }
     }
 
-    private ResolveInfo createForwardingResolveInfo(ForwardingIntentFilter fif, int userIdFrom) {
+    private ResolveInfo querySkipCurrentProfileIntents(
+            List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
+            int flags, int sourceUserId) {
+        if (matchingFilters != null) {
+            int size = matchingFilters.size();
+            for (int i = 0; i < size; i ++) {
+                CrossProfileIntentFilter filter = matchingFilters.get(i);
+                if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
+                    // Checking if there are activities in the target user that can handle the
+                    // intent.
+                    ResolveInfo resolveInfo = checkTargetCanHandle(filter, intent, resolvedType,
+                            flags, sourceUserId);
+                    if (resolveInfo != null) {
+                        return resolveInfo;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private ArrayList<ResolveInfo> queryIntentActivitiesCrossProfilePackage(
+            Intent intent, String resolvedType, int flags, int userId) {
+        ArrayList<ResolveInfo> matchingResolveInfos = new ArrayList<ResolveInfo>();
+        SparseArray<ArrayList<String>> sourceForwardingInfo =
+                mSettings.mCrossProfilePackageInfo.get(userId);
+        if (sourceForwardingInfo != null) {
+            int NI = sourceForwardingInfo.size();
+            for (int i = 0; i < NI; i++) {
+                int targetUserId = sourceForwardingInfo.keyAt(i);
+                ArrayList<String> packageNames = sourceForwardingInfo.valueAt(i);
+                List<ResolveInfo> resolveInfos = mActivities.queryIntent(
+                        intent, resolvedType, flags, targetUserId);
+                int NJ = resolveInfos.size();
+                for (int j = 0; j < NJ; j++) {
+                    ResolveInfo resolveInfo = resolveInfos.get(j);
+                    if (packageNames.contains(resolveInfo.activityInfo.packageName)) {
+                        matchingResolveInfos.add(createForwardingResolveInfo(
+                                resolveInfo.filter, userId, targetUserId));
+                    }
+                }
+            }
+        }
+        return matchingResolveInfos;
+    }
+
+    private ArrayList<ResolveInfo> queryIntentActivitiesCrossProfilePackage(
+            Intent intent, String resolvedType, int flags, int userId, PackageParser.Package pkg,
+            String packageName) {
+        ArrayList<ResolveInfo> matchingResolveInfos = new ArrayList<ResolveInfo>();
+        SparseArray<ArrayList<String>> sourceForwardingInfo =
+                mSettings.mCrossProfilePackageInfo.get(userId);
+        if (sourceForwardingInfo != null) {
+            int NI = sourceForwardingInfo.size();
+            for (int i = 0; i < NI; i++) {
+                int targetUserId = sourceForwardingInfo.keyAt(i);
+                if (sourceForwardingInfo.valueAt(i).contains(packageName)) {
+                    List<ResolveInfo> resolveInfos = mActivities.queryIntentForPackage(
+                            intent, resolvedType, flags, pkg.activities, targetUserId);
+                    int NJ = resolveInfos.size();
+                    for (int j = 0; j < NJ; j++) {
+                        ResolveInfo resolveInfo = resolveInfos.get(j);
+                        matchingResolveInfos.add(createForwardingResolveInfo(
+                                resolveInfo.filter, userId, targetUserId));
+                    }
+                }
+            }
+        }
+        return matchingResolveInfos;
+    }
+
+    // Return matching ResolveInfo if any for skip current profile intent filters.
+    private ResolveInfo queryCrossProfileIntents(
+            List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
+            int flags, int sourceUserId) {
+        if (matchingFilters != null) {
+            // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
+            // match the same intent. For performance reasons, it is better not to
+            // run queryIntent twice for the same userId
+            SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
+            int size = matchingFilters.size();
+            for (int i = 0; i < size; i++) {
+                CrossProfileIntentFilter filter = matchingFilters.get(i);
+                int targetUserId = filter.getTargetUserId();
+                if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) == 0
+                        && !alreadyTriedUserIds.get(targetUserId)) {
+                    // Checking if there are activities in the target user that can handle the
+                    // intent.
+                    ResolveInfo resolveInfo = checkTargetCanHandle(filter, intent, resolvedType,
+                            flags, sourceUserId);
+                    if (resolveInfo != null) return resolveInfo;
+                    alreadyTriedUserIds.put(targetUserId, true);
+                }
+            }
+        }
+        return null;
+    }
+
+    private ResolveInfo checkTargetCanHandle(CrossProfileIntentFilter filter, Intent intent,
+            String resolvedType, int flags, int sourceUserId) {
+        List<ResolveInfo> resultTargetUser = mActivities.queryIntent(intent,
+                resolvedType, flags, filter.getTargetUserId());
+        if (resultTargetUser != null) {
+            return createForwardingResolveInfo(filter, sourceUserId, filter.getTargetUserId());
+        }
+        return null;
+    }
+
+    private ResolveInfo createForwardingResolveInfo(IntentFilter filter,
+            int sourceUserId, int targetUserId) {
+        ResolveInfo forwardingResolveInfo = new ResolveInfo();
         String className;
-        int userIdDest = fif.getUserIdDest();
-        if (userIdDest == UserHandle.USER_OWNER) {
+        if (targetUserId == UserHandle.USER_OWNER) {
             className = FORWARD_INTENT_TO_USER_OWNER;
+            forwardingResolveInfo.showTargetUserIcon = true;
         } else {
             className = FORWARD_INTENT_TO_MANAGED_PROFILE;
         }
         ComponentName forwardingActivityComponentName = new ComponentName(
                 mAndroidApplication.packageName, className);
         ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
-                userIdFrom);
-        ResolveInfo forwardingResolveInfo = new ResolveInfo();
+                sourceUserId);
         forwardingResolveInfo.activityInfo = forwardingActivityInfo;
         forwardingResolveInfo.priority = 0;
         forwardingResolveInfo.preferredOrder = 0;
         forwardingResolveInfo.match = 0;
         forwardingResolveInfo.isDefault = true;
-        forwardingResolveInfo.filter = fif;
+        forwardingResolveInfo.filter = filter;
+        forwardingResolveInfo.targetUserId = targetUserId;
         return forwardingResolveInfo;
     }
 
@@ -4094,19 +4214,20 @@
     private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
             PackageParser.Package opkg) {
         if (!opkg.mTrustedOverlay) {
-            Slog.w(TAG, "Skipping target and overlay pair " + pkg.mScanPath + " and " +
-                    opkg.mScanPath + ": overlay not trusted");
+            Slog.w(TAG, "Skipping target and overlay pair " + pkg.codePath + " and " +
+                    opkg.codePath + ": overlay not trusted");
             return false;
         }
         HashMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName);
         if (overlaySet == null) {
-            Slog.e(TAG, "was about to create idmap for " + pkg.mScanPath + " and " +
-                    opkg.mScanPath + " but target package has no known overlays");
+            Slog.e(TAG, "was about to create idmap for " + pkg.codePath + " and " +
+                    opkg.codePath + " but target package has no known overlays");
             return false;
         }
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-        if (mInstaller.idmap(pkg.mScanPath, opkg.mScanPath, sharedGid) != 0) {
-            Slog.e(TAG, "Failed to generate idmap for " + pkg.mScanPath + " and " + opkg.mScanPath);
+        // TODO: generate idmap for split APKs
+        if (mInstaller.idmap(pkg.codePath, opkg.codePath, sharedGid) != 0) {
+            Slog.e(TAG, "Failed to generate idmap for " + pkg.codePath + " and " + opkg.codePath);
             return false;
         }
         PackageParser.Package[] overlayArray =
@@ -4146,7 +4267,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) {
@@ -4201,8 +4322,11 @@
             Log.i(TAG, srcFile.toString() + " changed; collecting certs");
         }
 
-        if (!pp.collectCertificates(pkg, parseFlags)) {
-            mLastScanError = pp.getParseError();
+        try {
+            pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
+        } catch (PackageParserException e) {
+            mLastScanError = e.error;
             return false;
         }
         return true;
@@ -4213,19 +4337,25 @@
      *  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);
         parseFlags |= mDefParseFlags;
-        PackageParser pp = new PackageParser(scanPath);
+        PackageParser pp = new PackageParser();
         pp.setSeparateProcesses(mSeparateProcesses);
         pp.setOnlyCoreApps(mOnlyCore);
-        final PackageParser.Package pkg = pp.parsePackage(scanFile,
-                scanPath, mMetrics, parseFlags, (scanMode & SCAN_TRUSTED_OVERLAY) != 0);
+        pp.setDisplayMetrics(mMetrics);
 
-        if (pkg == null) {
-            mLastScanError = pp.getParseError();
+        if ((scanMode & SCAN_TRUSTED_OVERLAY) != 0) {
+            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
+        }
+
+        final PackageParser.Package pkg;
+        try {
+            pkg = pp.parseMonolithicPackage(scanFile, parseFlags);
+        } catch (PackageParserException e) {
+            mLastScanError = e.error;
             return null;
         }
 
@@ -4281,7 +4411,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
@@ -4382,25 +4512,33 @@
             }
         }
 
-        String codePath = null;
+        final String codePath = pkg.codePath;
+        final String[] splitCodePaths = pkg.splitCodePaths;
+
         String resPath = null;
+        String[] splitResPaths = null;
         if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
             if (ps != null && ps.resourcePathString != null) {
                 resPath = ps.resourcePathString;
+                splitResPaths = deriveSplitResPaths(pkg.splitCodePaths);
             } else {
                 // Should not happen at all. Just log an error.
                 Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
             }
         } else {
-            resPath = pkg.mScanPath;
+            resPath = pkg.codePath;
+            splitResPaths = pkg.splitCodePaths;
         }
 
-        codePath = pkg.mScanPath;
         // Set application objects path explicitly.
-        setApplicationInfoPaths(pkg, codePath, resPath);
+        pkg.applicationInfo.sourceDir = codePath;
+        pkg.applicationInfo.publicSourceDir = resPath;
+        pkg.applicationInfo.splitSourceDirs = splitCodePaths;
+        pkg.applicationInfo.splitPublicSourceDirs = splitResPaths;
+
         // 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
@@ -4422,13 +4560,6 @@
         return scannedPkg;
     }
 
-    private static void setApplicationInfoPaths(PackageParser.Package pkg, String destCodePath,
-            String destResPath) {
-        pkg.mPath = pkg.mScanPath = destCodePath;
-        pkg.applicationInfo.sourceDir = destCodePath;
-        pkg.applicationInfo.publicSourceDir = destResPath;
-    }
-
     private static String fixProcessName(String defProcessName,
             String processName, int uid) {
         if (processName == null) {
@@ -4503,7 +4634,7 @@
             // The exception is first boot of a non-eng device, which
             // should do a full dexopt.
             boolean eng = "eng".equals(SystemProperties.get("ro.build.type"));
-            if (eng || !isFirstBoot()) {
+            if (eng || (!isFirstBoot() && mPackageUsage.isHistoricalPackageUsageAvailable())) {
                 // TODO: add a property to control this?
                 long dexOptLRUThresholdInMinutes;
                 if (eng) {
@@ -4652,52 +4783,51 @@
             }
         }
 
-        boolean performed = false;
-        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
-            String path = pkg.mScanPath;
-            try {
-                boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path,
-                                                                                pkg.packageName,
-                                                                                instructionSet,
-                                                                                defer);
-                // There are three basic cases here:
-                // 1.) we need to dexopt, either because we are forced or it is needed
-                // 2.) we are defering a needed dexopt
-                // 3.) we are skipping an unneeded dexopt
-                if (forceDex || (!defer && isDexOptNeededInternal)) {
-                    Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
-                    final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-                    int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
-                                                pkg.packageName, instructionSet);
-                    // Note that we ran dexopt, since rerunning will
-                    // probably just result in an error again.
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            final Collection<String> paths = pkg.getAllCodePaths();
+            for (String path : paths) {
+                try {
+                    boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path,
+                            pkg.packageName, instructionSet, defer);
+                    // There are three basic cases here:
+                    // 1.) we need to dexopt, either because we are forced or it is needed
+                    // 2.) we are defering a needed dexopt
+                    // 3.) we are skipping an unneeded dexopt
+                    if (forceDex || (!defer && isDexOptNeededInternal)) {
+                        Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
+                        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+                        int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
+                                                    pkg.packageName, instructionSet);
+                        // Note that we ran dexopt, since rerunning will
+                        // probably just result in an error again.
+                        pkg.mDexOptNeeded = false;
+                        if (ret < 0) {
+                            return DEX_OPT_FAILED;
+                        }
+                        return DEX_OPT_PERFORMED;
+                    }
+                    if (defer && isDexOptNeededInternal) {
+                        if (mDeferredDexOpt == null) {
+                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
+                        }
+                        mDeferredDexOpt.add(pkg);
+                        return DEX_OPT_DEFERRED;
+                    }
                     pkg.mDexOptNeeded = false;
-                    if (ret < 0) {
-                        return DEX_OPT_FAILED;
-                    }
-                    return DEX_OPT_PERFORMED;
+                    return DEX_OPT_SKIPPED;
+                } catch (FileNotFoundException e) {
+                    Slog.w(TAG, "Apk not found for dexopt: " + path);
+                    return DEX_OPT_FAILED;
+                } catch (IOException e) {
+                    Slog.w(TAG, "IOException reading apk: " + path, e);
+                    return DEX_OPT_FAILED;
+                } catch (StaleDexCacheError e) {
+                    Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
+                    return DEX_OPT_FAILED;
+                } catch (Exception e) {
+                    Slog.w(TAG, "Exception when doing dexopt : ", e);
+                    return DEX_OPT_FAILED;
                 }
-                if (defer && isDexOptNeededInternal) {
-                    if (mDeferredDexOpt == null) {
-                        mDeferredDexOpt = new HashSet<PackageParser.Package>();
-                    }
-                    mDeferredDexOpt.add(pkg);
-                    return DEX_OPT_DEFERRED;
-                }
-                pkg.mDexOptNeeded = false;
-                return DEX_OPT_SKIPPED;
-            } catch (FileNotFoundException e) {
-                Slog.w(TAG, "Apk not found for dexopt: " + path);
-                return DEX_OPT_FAILED;
-            } catch (IOException e) {
-                Slog.w(TAG, "IOException reading apk: " + path, e);
-                return DEX_OPT_FAILED;
-            } catch (StaleDexCacheError e) {
-                Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
-                return DEX_OPT_FAILED;
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception when doing dexopt : ", e);
-                return DEX_OPT_FAILED;
             }
         }
         return DEX_OPT_SKIPPED;
@@ -4827,11 +4957,11 @@
         return res;
     }
 
-    private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+    private void addSharedLibraryLPw(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
             PackageParser.Package changingLib) {
         if (file.path != null) {
-            mTmpSharedLibraries[num] = file.path;
-            return num+1;
+            usesLibraryFiles.add(file.path);
+            return;
         }
         PackageParser.Package p = mPackages.get(file.apk);
         if (changingLib != null && changingLib.packageName.equals(file.apk)) {
@@ -4844,16 +4974,8 @@
             }
         }
         if (p != null) {
-            String path = p.mPath;
-            for (int i=0; i<num; i++) {
-                if (mTmpSharedLibraries[i].equals(path)) {
-                    return num;
-                }
-            }
-            mTmpSharedLibraries[num] = p.mPath;
-            return num+1;
+            usesLibraryFiles.addAll(p.getAllCodePaths());
         }
-        return num;
     }
 
     private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
@@ -4867,11 +4989,7 @@
         }
 
         if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
-            if (mTmpSharedLibraries == null ||
-                    mTmpSharedLibraries.length < mSharedLibraries.size()) {
-                mTmpSharedLibraries = new String[mSharedLibraries.size()];
-            }
-            int num = 0;
+            final ArraySet<String> usesLibraryFiles = new ArraySet<>();
             int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
             for (int i=0; i<N; i++) {
                 final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
@@ -4882,7 +5000,7 @@
                     mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
                     return false;
                 }
-                num = addSharedLibraryLPw(file, num, changingLib);
+                addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
             }
             N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
             for (int i=0; i<N; i++) {
@@ -4892,13 +5010,12 @@
                             + " desires unavailable shared library "
                             + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
                 } else {
-                    num = addSharedLibraryLPw(file, num, changingLib);
+                    addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
                 }
             }
-            if (num > 0) {
-                pkg.usesLibraryFiles = new String[num];
-                System.arraycopy(mTmpSharedLibraries, 0,
-                        pkg.usesLibraryFiles, 0, num);
+            N = usesLibraryFiles.size();
+            if (N > 0) {
+                pkg.usesLibraryFiles = usesLibraryFiles.toArray(new String[N]);
             } else {
                 pkg.usesLibraryFiles = null;
             }
@@ -4943,9 +5060,9 @@
     }
 
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanMode, long currentTime, UserHandle user) {
-        File scanFile = new File(pkg.mScanPath);
-        if (scanFile == null || pkg.applicationInfo.sourceDir == null ||
+            int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
+        final File scanFile = new File(pkg.codePath);
+        if (pkg.applicationInfo.sourceDir == null ||
                 pkg.applicationInfo.publicSourceDir == null) {
             // Bail out. The resource and code paths haven't been set.
             Slog.w(TAG, " Code and resource paths haven't been set correctly");
@@ -5382,7 +5499,7 @@
             pkgSetting.uidError = uidError;
         }
 
-        String path = scanFile.getPath();
+        final String path = scanFile.getPath();
         /* Note: We don't want to unpack the native binaries for
          *        system applications, unless they have been updated
          *        (the binaries are already under /system/lib).
@@ -5394,7 +5511,23 @@
          *        only for non-system apps and system app upgrades.
          */
         if (pkg.applicationInfo.nativeLibraryDir != null) {
+            // TODO: extend to extract native code from split APKs
+            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 +5543,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 +5572,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 +5591,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,11 +5623,14 @@
                         }
                     }
                 }
+
+                pkgSetting.cpuAbiString = pkg.applicationInfo.cpuAbi;
             } catch (IOException ioe) {
                 Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
+            } finally {
+                handle.close();
             }
         }
-        pkg.mScanPath = path;
 
         if ((scanMode&SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
             // We don't do this here during boot because we can do it all
@@ -5630,7 +5775,7 @@
         synchronized (mPackages) {
             // We don't expect installation to fail beyond this point,
             if ((scanMode&SCAN_MONITOR) != 0) {
-                mAppDirs.put(pkg.mPath, pkg);
+                mAppDirs.put(pkg.codePath, pkg);
             }
             // Add the new setting to mSettings
             mSettings.insertPackageSettingLPw(pkgSetting, pkg);
@@ -5668,7 +5813,8 @@
             try {
                 ksm.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
                 if (pkg.mKeySetMapping != null) {
-                    for (Map.Entry<String, Set<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) {
+                    for (Map.Entry<String, ArraySet<PublicKey>> entry :
+                            pkg.mKeySetMapping.entrySet()) {
                         if (entry.getValue() != null) {
                             ksm.addDefinedKeySetToPackage(pkg.packageName,
                                 entry.getValue(), entry.getKey());
@@ -5920,6 +6066,8 @@
                 a.info.packageName = pkg.applicationInfo.packageName;
                 a.info.sourceDir = pkg.applicationInfo.sourceDir;
                 a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;
+                a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs;
+                a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs;
                 a.info.dataDir = pkg.applicationInfo.dataDir;
                 a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir;
                 mInstrumentation.put(a.getComponentName(), a);
@@ -6174,8 +6322,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 +6345,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) {
@@ -6256,8 +6399,8 @@
         // writer
         synchronized (mPackages) {
             mPackages.remove(pkg.applicationInfo.packageName);
-            if (pkg.mPath != null) {
-                mAppDirs.remove(pkg.mPath);
+            if (pkg.codePath != null) {
+                mAppDirs.remove(pkg.codePath);
             }
             cleanPackageDataStructuresLILPw(pkg, chatty);
         }
@@ -7535,7 +7678,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 +7799,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 +7848,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 +8554,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 +8570,9 @@
             this.installerPackageName = installerPackageName;
             this.verificationParams = verificationParams;
             this.encryptionParams = encryptionParams;
+            this.packageAbiOverride = packageAbiOverride;
+            this.packageInstructionSetOverride = (packageAbiOverride == null) ?
+                    packageAbiOverride : VMRuntime.getInstructionSet(packageAbiOverride);
         }
 
         @Override
@@ -8558,7 +8718,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 +8727,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 +9150,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 +9164,7 @@
             this.manifestDigest = manifestDigest;
             this.user = user;
             this.instructionSet = instructionSet;
+            this.abiOverride = abiOverride;
         }
 
         abstract void createCopyFile();
@@ -9019,6 +9181,10 @@
         abstract boolean doPostDeleteLI(boolean delete);
         abstract boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException;
 
+        String[] getSplitCodePaths() {
+            return null;
+        }
+
         /**
          * Called before the source arguments are copied. This is used mostly
          * for MoveParams when it needs to read the source file to put it in the
@@ -9058,12 +9224,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 +9239,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();
@@ -9106,10 +9273,6 @@
             }
         }
 
-        String getCodePath() {
-            return codeFileName;
-        }
-
         void createCopyFile() {
             installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
             codeFileName = createTempPackageFile(installDir).getPath();
@@ -9176,14 +9339,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;
@@ -9253,10 +9428,6 @@
             return status;
         }
 
-        String getResourcePath() {
-            return resourceFileName;
-        }
-
         private String getResourcePathFromCodePath() {
             final String codePath = getCodePath();
             if (isFwdLocked()) {
@@ -9287,6 +9458,16 @@
         }
 
         @Override
+        String getCodePath() {
+            return codeFileName;
+        }
+
+        @Override
+        String getResourcePath() {
+            return resourceFileName;
+        }
+
+        @Override
         String getNativeLibraryPath() {
             if (libraryPath == null) {
                 libraryPath = getLibraryPathFromCodePath();
@@ -9398,14 +9579,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 +9599,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 +9608,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 +9620,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 +9646,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);
             }
@@ -9670,7 +9853,7 @@
 
             return PackageManager.INSTALL_SUCCEEDED;
         }
-    };
+    }
 
     static String getAsecPackageName(String packageCid) {
         int idx = packageCid.lastIndexOf("-");
@@ -9751,6 +9934,20 @@
         return codePath.substring(sidx+1, eidx);
     }
 
+    private static String[] deriveSplitResPaths(String[] splitCodePaths) {
+        String[] splitResPaths = null;
+        if (!ArrayUtils.isEmpty(splitCodePaths)) {
+            splitResPaths = new String[splitCodePaths.length];
+            for (int i = 0; i < splitCodePaths.length; i++) {
+                final String splitCodePath = splitCodePaths[i];
+                final String resName = getApkName(splitCodePath) + ".zip";
+                splitResPaths[i] = new File(new File(splitCodePath).getParentFile(),
+                        resName).getAbsolutePath();
+            }
+        }
+        return splitResPaths;
+    }
+
     class PackageInstalledInfo {
         String name;
         int uid;
@@ -9772,7 +9969,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;
 
@@ -9790,7 +9987,7 @@
                 res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                 return;
             }
-            if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
+            if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.codePath)) {
                 // Don't allow installation over an existing package with the same name.
                 Slog.w(TAG, "Attempt to re-install " + pkgName
                         + " without first uninstalling.");
@@ -9800,17 +9997,14 @@
         }
         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);
+            Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
                 res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
             }
         } else {
-            updateSettingsLI(newPackage,
-                    installerPackageName,
-                    null, null,
-                    res);
+            updateSettingsLI(newPackage, installerPackageName, null, null, res);
             // delete the partially installed application. the data directory will have to be
             // restored if it was already existing
             if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -9827,7 +10021,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 +10050,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,17 +10087,14 @@
             // 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);
+                Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
                 if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
                     res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
                 }
             } else {
-                updateSettingsLI(newPackage,
-                        installerPackageName,
-                        allUsers, perUserInstalled,
-                        res);
+                updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
                 updatedSettings = true;
             }
         }
@@ -9920,9 +10113,9 @@
             }
             // 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);
+                File restoreFile = new File(deletedPackage.codePath);
                 // Parse old package
                 boolean oldOnSd = isExternal(deletedPackage);
                 int oldParseFlags  = mDefParseFlags | PackageParser.PARSE_CHATTY |
@@ -9931,7 +10124,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 +10144,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,9 +10198,9 @@
         // 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);
+            Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
                 res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
             }
@@ -10039,7 +10232,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) {
@@ -10053,10 +10246,11 @@
     }
 
     // Utility method used to move dex files during install.
-    private int moveDexFilesLI(PackageParser.Package newPackage) {
+    private int moveDexFilesLI(String oldCodePath, PackageParser.Package newPackage) {
+        // TODO: extend to move split APK dex files
         if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
             final String instructionSet = getAppInstructionSet(newPackage.applicationInfo);
-            int retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath,
+            int retCode = mInstaller.movedex(oldCodePath, newPackage.codePath,
                                              instructionSet);
             if (retCode != 0) {
                 /*
@@ -10068,8 +10262,8 @@
                  * file from a previous version of the package.
                  */
                 newPackage.mDexOptNeeded = true;
-                mInstaller.rmdex(newPackage.mScanPath, instructionSet);
-                mInstaller.rmdex(newPackage.mPath, instructionSet);
+                mInstaller.rmdex(oldCodePath, instructionSet);
+                mInstaller.rmdex(newPackage.codePath, instructionSet);
             }
         }
         return PackageManager.INSTALL_SUCCEEDED;
@@ -10087,13 +10281,7 @@
             mSettings.writeLPr();
         }
 
-        if ((res.returnCode = moveDexFilesLI(newPackage))
-                != PackageManager.INSTALL_SUCCEEDED) {
-            // Discontinue if moving dex files failed.
-            return;
-        }
-
-        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath);
 
         synchronized (mPackages) {
             updatePermissionsLPw(newPackage.packageName, newPackage,
@@ -10158,14 +10346,18 @@
         int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                 | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
-        PackageParser pp = new PackageParser(tmpPackageFile.getPath());
+        PackageParser pp = new PackageParser();
         pp.setSeparateProcesses(mSeparateProcesses);
-        final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
-                null, mMetrics, parseFlags);
-        if (pkg == null) {
-            res.returnCode = pp.getParseError();
+        pp.setDisplayMetrics(mMetrics);
+
+        final PackageParser.Package pkg;
+        try {
+            pkg = pp.parseMonolithicPackage(tmpPackageFile, parseFlags);
+        } catch (PackageParserException e) {
+            res.returnCode = e.error;
             return;
         }
+
         String pkgName = res.name = pkg.packageName;
         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
             if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
@@ -10173,8 +10365,12 @@
                 return;
             }
         }
-        if (!pp.collectCertificates(pkg, parseFlags)) {
-            res.returnCode = pp.getParseError();
+
+        try {
+            pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
+        } catch (PackageParserException e) {
+            res.returnCode = e.error;
             return;
         }
 
@@ -10279,14 +10475,19 @@
             return;
         }
         // Set application objects path explicitly after the rename
-        setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
+        pkg.codePath = args.getCodePath();
+        pkg.applicationInfo.sourceDir = args.getCodePath();
+        pkg.applicationInfo.publicSourceDir = args.getResourcePath();
+        pkg.applicationInfo.splitSourceDirs = args.getSplitCodePaths();
+        pkg.applicationInfo.splitPublicSourceDirs = deriveSplitResPaths(
+                pkg.applicationInfo.splitSourceDirs);
         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 +10904,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
@@ -11159,7 +11360,7 @@
                 publicSrcDir = applicationInfo.publicSourceDir;
             }
         }
-        int res = mInstaller.getSizeInfo(packageName, userHandle, p.mPath, libDirPath,
+        int res = mInstaller.getSizeInfo(packageName, userHandle, p.codePath, libDirPath,
                 publicSrcDir, asecPath, getAppInstructionSetFromSettings(ps),
                 pStats);
         if (res < 0) {
@@ -11482,33 +11683,53 @@
     }
 
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter intentFilter, int sourceUserId,
+            int targetUserId, int flags) {
         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");
+        if (intentFilter.countActions() == 0) {
+            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);
+            CrossProfileIntentFilter filter = new CrossProfileIntentFilter(intentFilter,
+                    targetUserId, flags);
+            mSettings.editCrossProfileIntentResolverLPw(sourceUserId).addFilter(filter);
+            mSettings.writePackageRestrictionsLPr(sourceUserId);
         }
     }
 
+    public void addCrossProfileIntentsForPackage(String packageName,
+            int sourceUserId, int targetUserId) {
+        mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        mSettings.addCrossProfilePackage(packageName, sourceUserId, targetUserId);
+        mSettings.writePackageRestrictionsLPr(sourceUserId);
+    }
+
+    public void removeCrossProfileIntentsForPackage(String packageName,
+            int sourceUserId, int targetUserId) {
+        mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        mSettings.removeCrossProfilePackage(packageName, sourceUserId, 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 resolver =
+                    mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
+            HashSet<CrossProfileIntentFilter> set =
+                    new HashSet<CrossProfileIntentFilter>(resolver.filterSet());
+            for (CrossProfileIntentFilter filter : set) {
+                if ((filter.getFlags() & PackageManager.SET_BY_PROFILE_OWNER) != 0) {
+                    resolver.removeFilter(filter);
+                }
             }
-            mSettings.writePackageRestrictionsLPr(userIdOrig);
+            mSettings.writePackageRestrictionsLPr(sourceUserId);
         }
     }
 
@@ -12515,7 +12736,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) {
                         /*
@@ -12791,7 +13012,7 @@
                                             + " Aborting move and returning error");
                                     returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR;
                                 } else {
-                                    final String oldCodePath = pkg.mPath;
+                                    final String oldCodePath = pkg.codePath;
                                     final String newCodePath = mp.targetArgs.getCodePath();
                                     final String newResPath = mp.targetArgs.getResourcePath();
                                     final String newNativePath = mp.targetArgs
@@ -12821,18 +13042,17 @@
                                     }
 
                                     if (returnCode == PackageManager.MOVE_SUCCEEDED) {
-                                        pkg.mPath = newCodePath;
+                                        pkg.codePath = newCodePath;
                                         // Move dex files around
-                                        if (moveDexFilesLI(pkg) != PackageManager.INSTALL_SUCCEEDED) {
+                                        if (moveDexFilesLI(oldCodePath, pkg) != PackageManager.INSTALL_SUCCEEDED) {
                                             // Moving of dex files failed. Set
                                             // error code and abort move.
-                                            pkg.mPath = pkg.mScanPath;
+                                            pkg.codePath = oldCodePath;
                                             returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
                                         }
                                     }
 
                                     if (returnCode == PackageManager.MOVE_SUCCEEDED) {
-                                        pkg.mScanPath = newCodePath;
                                         pkg.applicationInfo.sourceDir = newCodePath;
                                         pkg.applicationInfo.publicSourceDir = newResPath;
                                         pkg.applicationInfo.nativeLibraryDir = newNativePath;
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..1867ff3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -31,12 +31,14 @@
 import android.net.Uri;
 import android.os.PatternMatcher;
 import android.util.LogPrinter;
+
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.PackageManagerService.DumpState;
 
 import java.util.Collection;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -132,6 +134,13 @@
     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";
+    private static final String TAG_CROSS_PROFILE_PACKAGE_INFO = "cross-profile-package-info";
+    private static final String CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID = "target-user-id";
+    private static final String CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME = "package-name";
+    private static final String CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME = "value";
+    //Old name. Kept for compatibility
     static final String TAG_FORWARDING_INTENT_FILTERS =
             "forwarding-intent-filters";
 
@@ -189,8 +198,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>();
@@ -237,6 +246,11 @@
 
     public final KeySetManager mKeySetManager = new KeySetManager(mPackages);
 
+    // A mapping of (sourceUserId, targetUserId, packageNames) for forwarding the intents of a
+    // package.
+    final SparseArray<SparseArray<ArrayList<String>>>
+            mCrossProfilePackageInfo = new SparseArray<SparseArray<ArrayList<String>>>();
+
     Settings(Context context) {
         this(context, Environment.getDataDirectory());
     }
@@ -259,6 +273,47 @@
         mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
     }
 
+    public void addCrossProfilePackage(
+            String packageName, int sourceUserId, int targetUserId) {
+        synchronized(mCrossProfilePackageInfo) {
+            SparseArray<ArrayList<String>> sourceForwardingInfo =
+                    mCrossProfilePackageInfo.get(sourceUserId);
+            if (sourceForwardingInfo == null) {
+                sourceForwardingInfo = new SparseArray<ArrayList<String>>();
+                mCrossProfilePackageInfo.put(sourceUserId, sourceForwardingInfo);
+            }
+            ArrayList<String> packageNames = sourceForwardingInfo.get(targetUserId);
+            if (packageNames == null) {
+                packageNames = new ArrayList<String>();
+                sourceForwardingInfo.put(targetUserId, packageNames);
+            }
+            if (!packageNames.contains(packageName)) {
+                packageNames.add(packageName);
+            }
+        }
+    }
+
+    public void removeCrossProfilePackage(
+            String packageName, int sourceUserId, int targetUserId) {
+        synchronized(mCrossProfilePackageInfo) {
+            SparseArray<ArrayList<String>> sourceForwardingInfo =
+                    mCrossProfilePackageInfo.get(sourceUserId);
+            if (sourceForwardingInfo == null) {
+                return;
+            }
+            ArrayList<String> packageNames = sourceForwardingInfo.get(targetUserId);
+            if (packageNames != null && packageNames.contains(packageName)) {
+                packageNames.remove(packageName);
+                if (packageNames.isEmpty()) {
+                    sourceForwardingInfo.remove(targetUserId);
+                    if (sourceForwardingInfo.size() == 0) {
+                        mCrossProfilePackageInfo.remove(sourceUserId);
+                    }
+                }
+            }
+        }
+    }
+
     PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
             String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
             String nativeLibraryPathString, String cpuAbiString, int pkgFlags, UserHandle user, boolean add) {
@@ -856,13 +911,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 +1035,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 +1046,72 @@
             }
             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);
+            }
+        }
+    }
+
+    private void readCrossProfilePackageInfoLPw(XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String targetUserIdString = parser.getAttributeValue(null,
+                        CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID);
+                if (targetUserIdString == null) {
+                    String msg = "Missing element under " + TAG +": "
+                            + CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID + " at " +
+                            parser.getPositionDescription();
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    continue;
+                }
+                int targetUserId = Integer.parseInt(targetUserIdString);
+                readCrossProfilePackageInfoForTargetLPw(parser, userId, targetUserId);
+            } else {
+                String msg = "Unknown element under " +  TAG_CROSS_PROFILE_PACKAGE_INFO + ": " +
+                        parser.getName();
+                PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readCrossProfilePackageInfoForTargetLPw(
+            XmlPullParser parser, int sourceUserId, int targetUserId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals(CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME)) {
+                String packageName = parser.getAttributeValue(
+                        null, CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME);
+                if (packageName == null) {
+                    String msg = "Missing element under " + TAG +": "
+                            + CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME + " at " +
+                            parser.getPositionDescription();
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    continue;
+                }
+                addCrossProfilePackage(packageName, sourceUserId, targetUserId);
+            } else {
+                String msg = "Unknown element under " +  TAG_ITEM + ": " +
                         parser.getName();
                 PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                 XmlUtils.skipCurrentTag(parser);
@@ -1130,8 +1247,11 @@
                     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 if (tagName.equals(TAG_CROSS_PROFILE_PACKAGE_INFO)){
+                    readCrossProfilePackageInfoLPw(parser, userId);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
                           + parser.getName());
@@ -1209,18 +1329,44 @@
         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 writeCrossProfilePackageInfoLPr(XmlSerializer serializer, int userId)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        SparseArray<ArrayList<String>> sourceForwardingInfo = mCrossProfilePackageInfo.get(userId);
+        if (sourceForwardingInfo == null) {
+            return;
+        }
+        serializer.startTag(null, TAG_CROSS_PROFILE_PACKAGE_INFO);
+        int NI = sourceForwardingInfo.size();
+        for (int i = 0; i < NI; i++) {
+            int targetUserId = sourceForwardingInfo.keyAt(i);
+            ArrayList<String> packageNames = sourceForwardingInfo.valueAt(i);
+            serializer.startTag(null, TAG_ITEM);
+            serializer.attribute(null, CROSS_PROFILE_PACKAGE_INFO_ATTR_TARGET_USER_ID,
+                    Integer.toString(targetUserId));
+            int NJ = packageNames.size();
+            for (int j = 0; j < NJ; j++) {
+                serializer.startTag(null, CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME);
+                serializer.attribute(null, CROSS_PROFILE_PACKAGE_INFO_ATTR_PACKAGE_NAME,
+                        packageNames.get(j));
+                serializer.endTag(null, CROSS_PROFILE_PACKAGE_INFO_TAG_PACKAGE_NAME);
+            }
+            serializer.endTag(null, TAG_ITEM);
+        }
+        serializer.endTag(null, TAG_CROSS_PROFILE_PACKAGE_INFO);
     }
 
     void writePackageRestrictionsLPr(int userId) {
@@ -1321,7 +1467,9 @@
 
             writePersistentPreferredActivitiesLPr(serializer, userId);
 
-            writeForwardingIntentFiltersLPr(serializer, userId);
+            writeCrossProfileIntentFiltersLPr(serializer, userId);
+
+            writeCrossProfilePackageInfoLPr(serializer, userId);
 
             serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
 
@@ -1940,10 +2088,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 +3084,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..832a3b6 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;
 
@@ -412,13 +412,17 @@
 
     @Override
     public Bitmap getUserIcon(int userId) {
-        checkManageUsersPermission("read users");
         synchronized (mPackagesLock) {
             UserInfo info = mUsers.get(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "getUserIcon: unknown user #" + userId);
                 return null;
             }
+            int callingGroupId = mUsers.get(UserHandle.getCallingUserId()).profileGroupId;
+            if (callingGroupId == UserInfo.NO_PROFILE_GROUP_ID
+                    || callingGroupId != info.profileGroupId) {
+                checkManageUsersPermission("get the icon of a user who is not related");
+            }
             if (info.iconPath == null) {
                 return null;
             }
@@ -426,43 +430,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 +549,6 @@
     }
 
     private void readUserListLocked() {
-        mGuestEnabled = false;
         if (!mUserListFile.exists()) {
             fallbackToSingleUserLocked();
             return;
@@ -624,9 +590,6 @@
 
                     if (user != null) {
                         mUsers.put(user.id, user);
-                        if (user.isGuest()) {
-                            mGuestEnabled = true;
-                        }
                         if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
                             mNextSerialNumber = user.id + 1;
                         }
@@ -785,7 +748,7 @@
                 writeBoolean(serializer, restrictions, UserManager.ENSURE_VERIFY_APPS);
                 writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
                 writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-                writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_APPS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_APPS_CONTROL);
                 writeBoolean(serializer, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
                 writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
                 writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADJUST_VOLUME);
@@ -937,7 +900,7 @@
                         readBoolean(parser, restrictions, UserManager.ENSURE_VERIFY_APPS);
                         readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
                         readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-                        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_APPS);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_APPS_CONTROL);
                         readBoolean(parser, restrictions,
                                 UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
                         readBoolean(parser, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
@@ -1084,6 +1047,11 @@
     }
 
     private UserInfo createUserInternal(String name, int flags, int parentId) {
+        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
+                UserManager.DISALLOW_ADD_USER, false)) {
+            Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
+            return null;
+        }
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo = null;
         try {
@@ -1140,53 +1108,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 || mRemovingUserIds.get(userHandle)) {
+                    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 +1501,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 +1552,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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index fb4b8f0..bd80b54 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -19,10 +19,10 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
-import com.android.server.BatteryService;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
+import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.Watchdog;
@@ -42,6 +42,7 @@
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.net.Uri;
 import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -163,13 +164,14 @@
     private static final int POWER_HINT_LOW_POWER_MODE = 5;
 
     private final Context mContext;
+    private final ServiceThread mHandlerThread;
+    private final PowerManagerHandler mHandler;
+
     private LightsManager mLightsManager;
-    private BatteryService mBatteryService;
+    private BatteryManagerInternal mBatteryManagerInternal;
     private DisplayManagerInternal mDisplayManagerInternal;
     private IBatteryStats mBatteryStats;
     private IAppOpsService mAppOps;
-    private ServiceThread mHandlerThread;
-    private PowerManagerHandler mHandler;
     private WindowManagerPolicy mPolicy;
     private Notifier mNotifier;
     private WirelessChargerDetector mWirelessChargerDetector;
@@ -429,6 +431,11 @@
     public PowerManagerService(Context context) {
         super(context);
         mContext = context;
+        mHandlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
+        mHandlerThread.start();
+        mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+
         synchronized (mLock) {
             mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
             mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
@@ -451,39 +458,19 @@
     public void onStart() {
         publishBinderService(Context.POWER_SERVICE, new BinderService());
         publishLocalService(PowerManagerInternal.class, new LocalService());
-    }
-
-    /**
-     * Initialize the power manager.
-     * Must be called before any other functions within the power manager are called.
-     */
-    public void init(LightsManager ls,
-            BatteryService bs, IBatteryStats bss,
-            IAppOpsService appOps) {
-        mLightsManager = ls;
-        mBatteryService = bs;
-        mBatteryStats = bss;
-        mAppOps = appOps;
-        mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
-        mHandlerThread = new ServiceThread(TAG,
-                Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
-        mHandlerThread.start();
-        mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
 
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
     }
 
-    void setPolicy(WindowManagerPolicy policy) {
-        synchronized (mLock) {
-            mPolicy = policy;
-        }
-    }
-
-    public void systemReady() {
+    public void systemReady(IAppOpsService appOps) {
         synchronized (mLock) {
             mSystemReady = true;
-            mDreamManager = LocalServices.getService(DreamManagerInternal.class);
+            mAppOps = appOps;
+            mDreamManager = getLocalService(DreamManagerInternal.class);
+            mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
+            mPolicy = getLocalService(WindowManagerPolicy.class);
+            mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
 
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
@@ -494,6 +481,7 @@
 
             // The notifier runs on the system server's main looper so as not to interfere
             // with the animations and other critical functions of the power manager.
+            mBatteryStats = BatteryStatsService.getService();
             mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats,
                     mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
                     mScreenOnBlocker, mPolicy);
@@ -502,6 +490,8 @@
                     createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
                     mHandler);
             mSettingsObserver = new SettingsObserver(mHandler);
+
+            mLightsManager = getLocalService(LightsManager.class);
             mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
             // Initialize display power management.
@@ -1168,10 +1158,10 @@
             final boolean wasPowered = mIsPowered;
             final int oldPlugType = mPlugType;
             final boolean oldLevelLow = mBatteryLevelLow;
-            mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-            mPlugType = mBatteryService.getPlugType();
-            mBatteryLevel = mBatteryService.getBatteryLevel();
-            mBatteryLevelLow = mBatteryService.getBatteryLevelLow();
+            mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+            mPlugType = mBatteryManagerInternal.getPlugType();
+            mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+            mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
@@ -1254,7 +1244,7 @@
             final boolean wasStayOn = mStayOn;
             if (mStayOnWhilePluggedInSetting != 0
                     && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
-                mStayOn = mBatteryService.isPowered(mStayOnWhilePluggedInSetting);
+                mStayOn = mBatteryManagerInternal.isPowered(mStayOnWhilePluggedInSetting);
             } else {
                 mStayOn = false;
             }
@@ -3076,10 +3066,5 @@
                 mLowPowerModeListeners.add(listener);
             }
         }
-
-        @Override
-        public void setPolicy(WindowManagerPolicy policy) {
-            PowerManagerService.this.setPolicy(policy);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 8d905ba..dc44e51 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -23,7 +23,4 @@
 
 public interface StatusBarManagerInternal {
     void setNotificationDelegate(NotificationDelegate delegate);
-    void addNotification(StatusBarNotification notification);
-    void updateNotification(StatusBarNotification notification);
-    void removeNotification(String key);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 55b5e3b..e548fa5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -60,8 +60,6 @@
     private NotificationDelegate mNotificationDelegate;
     private volatile IStatusBar mBar;
     private StatusBarIconList mIcons = new StatusBarIconList();
-    private HashMap<String,StatusBarNotification> mNotifications
-            = new HashMap<String,StatusBarNotification>();
 
     // for disabling the status bar
     private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
@@ -111,56 +109,7 @@
     private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
         @Override
         public void setNotificationDelegate(NotificationDelegate delegate) {
-            synchronized (mNotifications) {
-                mNotificationDelegate = delegate;
-            }
-        }
-
-        @Override
-        public void addNotification(StatusBarNotification notification) {
-            synchronized (mNotifications) {
-                mNotifications.put(notification.getKey(), notification);
-                if (mBar != null) {
-                    try {
-                        mBar.addNotification(notification);
-                    } catch (RemoteException ex) {
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void updateNotification(StatusBarNotification notification) {
-            synchronized (mNotifications) {
-                String key = notification.getKey();
-                if (!mNotifications.containsKey(key)) {
-                    throw new IllegalArgumentException("updateNotification key not found: " + key);
-                }
-                mNotifications.put(notification.getKey(), notification);
-                if (mBar != null) {
-                    try {
-                        mBar.updateNotification(notification);
-                    } catch (RemoteException ex) {
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void removeNotification(String key) {
-            synchronized (mNotifications) {
-                final StatusBarNotification n = mNotifications.remove(key);
-                if (n == null) {
-                    Slog.e(TAG, "removeNotification key not found: " + key);
-                    return;
-                }
-                if (mBar != null) {
-                    try {
-                        mBar.removeNotification(key);
-                    } catch (RemoteException ex) {
-                    }
-                }
-            }
+            mNotificationDelegate = delegate;
         }
     };
 
@@ -511,7 +460,7 @@
     // ================================================================================
     @Override
     public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
-            List<StatusBarNotification> notifications, int switches[], List<IBinder> binders) {
+            int switches[], List<IBinder> binders) {
         enforceStatusBarService();
 
         Slog.i(TAG, "registerStatusBar bar=" + bar);
@@ -519,11 +468,6 @@
         synchronized (mIcons) {
             iconList.copyFrom(mIcons);
         }
-        synchronized (mNotifications) {
-            for (StatusBarNotification sbn : mNotifications.values()) {
-                notifications.add(sbn);
-            }
-        }
         synchronized (mLock) {
             switches[0] = gatherDisableActionsLocked(mCurrentUserId);
             switches[1] = mSystemUiVisibility;
@@ -708,15 +652,6 @@
             mIcons.dump(pw);
         }
 
-        synchronized (mNotifications) {
-            int i=0;
-            pw.println("Notification list:");
-            for (Map.Entry<String,StatusBarNotification> e: mNotifications.entrySet()) {
-                pw.printf("  %2d: %s\n", i, e.getValue().toString());
-                i++;
-            }
-        }
-
         synchronized (mLock) {
             pw.println("  mDisabled=0x" + Integer.toHexString(mDisabled));
             final int N = mDisableRecords.size();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 462b234..1aec569 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;
@@ -87,6 +88,7 @@
     private static final int MSG_UNREGISTER_LISTENER = 2;
     private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3;
     private static final int MSG_ENABLED_AGENTS_CHANGED = 4;
+    private static final int MSG_REQUIRE_CREDENTIAL_ENTRY = 5;
 
     private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<AgentInfo>();
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>();
@@ -313,6 +315,17 @@
         }
     }
 
+
+    private void requireCredentialEntry(int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            mUserHasAuthenticatedSinceBoot.clear();
+            updateTrustAll();
+        } else {
+            mUserHasAuthenticatedSinceBoot.put(userId, false);
+            updateTrust(userId);
+        }
+    }
+
     // Listeners
 
     private void addListener(ITrustListener listener) {
@@ -337,8 +350,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);
             }
         }
     }
@@ -362,6 +379,17 @@
         }
 
         @Override
+        public void reportRequireCredentialEntry(int userId) throws RemoteException {
+            enforceReportPermission();
+            if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) {
+                mHandler.obtainMessage(MSG_REQUIRE_CREDENTIAL_ENTRY, userId, 0).sendToTarget();
+            } else {
+                throw new IllegalArgumentException(
+                        "userId must be an explicit user id or USER_ALL");
+            }
+        }
+
+        @Override
         public void registerTrustListener(ITrustListener trustListener) throws RemoteException {
             enforceListenerPermission();
             mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget();
@@ -374,8 +402,8 @@
         }
 
         private void enforceReportPermission() {
-            mContext.enforceCallingPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE,
-                    "reporting trust events");
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
         }
 
         private void enforceListenerPermission() {
@@ -455,6 +483,9 @@
                 case MSG_ENABLED_AGENTS_CHANGED:
                     refreshAgentList();
                     break;
+                case MSG_REQUIRE_CREDENTIAL_ENTRY:
+                    requireCredentialEntry(msg.arg1);
+                    break;
             }
         }
     };
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index 34168a8..23c0a4c 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -20,12 +20,19 @@
 import android.media.tv.TvStreamConfig;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Message;
 import android.view.Surface;
+import android.util.Slog;
+
+import java.util.LinkedList;
+import java.util.Queue;
 
 /**
  * Provides access to the low-level TV input hardware abstraction layer.
  */
-final class TvInputHal {
+final class TvInputHal implements Handler.Callback {
+    private final static String TAG = TvInputHal.class.getSimpleName();
+
     public final static int SUCCESS = 0;
     public final static int ERROR_NO_INIT = -1;
     public final static int ERROR_STALE_CONFIG = -2;
@@ -35,6 +42,12 @@
     public static final int TYPE_BUILT_IN_TUNER = 2;
     public static final int TYPE_PASSTHROUGH = 3;
 
+    public static final int EVENT_OPEN = 0;
+    // Below should be in sync with hardware/libhardware/include/hardware/tv_input.h
+    public static final int EVENT_DEVICE_AVAILABLE = 1;
+    public static final int EVENT_DEVICE_UNAVAILABLE = 2;
+    public static final int EVENT_STREAM_CONFIGURATION_CHANGED = 3;
+
     public interface Callback {
         public void onDeviceAvailable(
                 TvInputHardwareInfo info, TvStreamConfig[] configs);
@@ -50,7 +63,7 @@
             int generation);
     private static native void nativeClose(long ptr);
 
-    private long mPtr = 0l;
+    private volatile long mPtr = 0;
     private final Callback mCallback;
     private final HandlerThread mThread = new HandlerThread("TV input HAL event thread");
     private final Handler mHandler;
@@ -60,21 +73,23 @@
     public TvInputHal(Callback callback) {
         mCallback = callback;
         mThread.start();
-        mHandler = new Handler(mThread.getLooper());
+        mHandler = new Handler(mThread.getLooper(), this);
     }
 
     public void init() {
         mPtr = nativeOpen();
+        mHandler.sendEmptyMessage(EVENT_OPEN);
     }
 
     public int setSurface(int deviceId, Surface surface, TvStreamConfig streamConfig) {
-        if (mPtr == 0) {
+        long ptr = mPtr;
+        if (ptr == 0) {
             return ERROR_NO_INIT;
         }
         if (mStreamConfigGeneration != streamConfig.getGeneration()) {
             return ERROR_STALE_CONFIG;
         }
-        if (nativeSetSurface(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
+        if (nativeSetSurface(ptr, deviceId, streamConfig.getStreamId(), surface) == 0) {
             return SUCCESS;
         } else {
             return ERROR_UNKNOWN;
@@ -82,47 +97,81 @@
     }
 
     public void close() {
-        if (mPtr != 0l) {
-            nativeClose(mPtr);
+        long ptr = mPtr;
+        if (ptr != 0l) {
+            nativeClose(ptr);
             mThread.quitSafely();
         }
     }
 
-    private synchronized void retrieveStreamConfigs(int deviceId) {
+    private synchronized void retrieveStreamConfigs(long ptr, int deviceId) {
         ++mStreamConfigGeneration;
-        mStreamConfigs = nativeGetStreamConfigs(mPtr, deviceId, mStreamConfigGeneration);
+        mStreamConfigs = nativeGetStreamConfigs(ptr, deviceId, mStreamConfigGeneration);
     }
 
     // Called from native
-    private void deviceAvailableFromNative(int deviceId, int type) {
-        final TvInputHardwareInfo info = new TvInputHardwareInfo(deviceId, type);
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                retrieveStreamConfigs(info.getDeviceId());
-                mCallback.onDeviceAvailable(info, mStreamConfigs);
-            }
-        });
+    private void deviceAvailableFromNative(TvInputHardwareInfo info) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_DEVICE_AVAILABLE, info));
     }
 
     private void deviceUnavailableFromNative(int deviceId) {
-        final int id = deviceId;
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mCallback.onDeviceUnavailable(id);
-            }
-        });
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_DEVICE_UNAVAILABLE, deviceId, 0));
     }
 
     private void streamConfigsChangedFromNative(int deviceId) {
-        final int id = deviceId;
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                retrieveStreamConfigs(id);
-                mCallback.onStreamConfigurationChanged(id, mStreamConfigs);
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, 0));
+    }
+
+    // Handler.Callback implementation
+
+    private Queue<Message> mPendingMessageQueue = new LinkedList<Message>();
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        long ptr = mPtr;
+        if (ptr == 0) {
+            mPendingMessageQueue.add(msg);
+            return true;
+        }
+        while (!mPendingMessageQueue.isEmpty()) {
+            handleMessageInternal(ptr, mPendingMessageQueue.remove());
+        }
+        handleMessageInternal(ptr, msg);
+        return true;
+    }
+
+    private void handleMessageInternal(long ptr, Message msg) {
+        switch (msg.what) {
+            case EVENT_OPEN:
+                // No-op
+                break;
+
+            case EVENT_DEVICE_AVAILABLE: {
+                TvInputHardwareInfo info = (TvInputHardwareInfo)msg.obj;
+                retrieveStreamConfigs(ptr, info.getDeviceId());
+                mCallback.onDeviceAvailable(info, mStreamConfigs);
+                break;
             }
-        });
+
+            case EVENT_DEVICE_UNAVAILABLE: {
+                int deviceId = msg.arg1;
+                mCallback.onDeviceUnavailable(deviceId);
+                break;
+            }
+
+            case EVENT_STREAM_CONFIGURATION_CHANGED: {
+                int deviceId = msg.arg1;
+                retrieveStreamConfigs(ptr, deviceId);
+                mCallback.onStreamConfigurationChanged(deviceId, mStreamConfigs);
+                break;
+            }
+
+            default:
+                Slog.e(TAG, "Unknown event: " + msg);
+                break;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index e34f42b..1146f0f 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -17,6 +17,11 @@
 package com.android.server.tv;
 
 import android.content.Context;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.media.AudioPatch;
+import android.media.AudioPort;
+import android.media.AudioPortConfig;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.TvInputHardwareInfo;
@@ -48,11 +53,13 @@
     private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
     private final Context mContext;
     private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
+    private final AudioManager mAudioManager;
 
     private final Object mLock = new Object();
 
     public TvInputHardwareManager(Context context) {
         mContext = context;
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
         // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
         mHal.init();
@@ -116,6 +123,19 @@
         }
     }
 
+    private boolean checkUidChangedLocked(
+            Connection connection, int callingUid, int resolvedUserId) {
+        Integer connectionCallingUid = connection.getCallingUidLocked();
+        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
+        if (connectionCallingUid == null || connectionResolvedUserId == null) {
+            return true;
+        }
+        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Create a TvInputHardware object with a specific deviceId. One service at a time can access
      * the object, and if more than one process attempts to create hardware with the same deviceId,
@@ -133,8 +153,7 @@
                 Slog.e(TAG, "Invalid deviceId : " + deviceId);
                 return null;
             }
-            if (connection.getCallingUidLocked() != callingUid
-                    || connection.getResolvedUserIdLocked() != resolvedUserId) {
+            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
                 try {
                     callback.asBinder().linkToDeath(connection, 0);
@@ -160,8 +179,7 @@
                 return;
             }
             if (connection.getHardwareLocked() != hardware
-                    || connection.getCallingUidLocked() != callingUid
-                    || connection.getResolvedUserIdLocked() != resolvedUserId) {
+                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 return;
             }
             connection.resetLocked(null, null, null, null);
@@ -226,11 +244,11 @@
             return mConfigs;
         }
 
-        public int getCallingUidLocked() {
+        public Integer getCallingUidLocked() {
             return mCallingUid;
         }
 
-        public int getResolvedUserIdLocked() {
+        public Integer getResolvedUserIdLocked() {
             return mResolvedUserId;
         }
 
@@ -247,12 +265,48 @@
         private boolean mReleased = false;
         private final Object mImplLock = new Object();
 
+        private final AudioDevicePort mAudioSource;
+        private final AudioDevicePort mAudioSink;
+        private AudioPatch mAudioPatch = null;
+
         public TvInputHardwareImpl(TvInputHardwareInfo info) {
             mInfo = info;
+            AudioDevicePort audioSource = null;
+            AudioDevicePort audioSink = null;
+            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
+                ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
+                if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
+                    // Find source
+                    for (AudioPort port : devicePorts) {
+                        AudioDevicePort devicePort = (AudioDevicePort) port;
+                        if (devicePort.type() == mInfo.getAudioType() &&
+                                devicePort.address().equals(mInfo.getAudioAddress())) {
+                            audioSource = devicePort;
+                            break;
+                        }
+                    }
+                    // Find sink
+                    // TODO: App may want to specify sink device?
+                    int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
+                    for (AudioPort port : devicePorts) {
+                        AudioDevicePort devicePort = (AudioDevicePort) port;
+                        if (devicePort.type() == sinkDevices) {
+                            audioSink = devicePort;
+                            break;
+                        }
+                    }
+                }
+            }
+            mAudioSource = audioSource;
+            mAudioSink = audioSink;
         }
 
         public void release() {
             synchronized (mImplLock) {
+                if (mAudioPatch != null) {
+                    mAudioManager.releaseAudioPatch(mAudioPatch);
+                    mAudioPatch = null;
+                }
                 mReleased = true;
             }
         }
@@ -277,6 +331,22 @@
                         }
                     }
                 }
+                if (mAudioSource != null && mAudioSink != null) {
+                    if (surface != null) {
+                        AudioPortConfig sourceConfig = mAudioSource.activeConfig();
+                        AudioPortConfig sinkConfig = mAudioSink.activeConfig();
+                        AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
+                        // TODO: build config if activeConfig() == null
+                        mAudioManager.createAudioPatch(
+                                audioPatchArray,
+                                new AudioPortConfig[] { sourceConfig },
+                                new AudioPortConfig[] { sinkConfig });
+                        mAudioPatch = audioPatchArray[0];
+                    } else {
+                        mAudioManager.releaseAudioPatch(mAudioPatch);
+                        mAudioPatch = null;
+                    }
+                }
                 return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
             }
         }
@@ -288,7 +358,7 @@
                     throw new IllegalStateException("Device already released.");
                 }
             }
-            // TODO
+            // TODO: Use AudioGain?
         }
 
         @Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 7a95823..61fdaccb 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,12 +19,15 @@
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.OperationApplicationException;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -66,11 +69,12 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /** This class provides a system service that manages television inputs. */
 public final class TvInputManagerService extends SystemService {
@@ -131,8 +135,46 @@
                     buildTvInputListLocked(mCurrentUserId);
                 }
             }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                synchronized (mLock) {
+                    UserState userState = getUserStateLocked(mCurrentUserId);
+                    if (!userState.packageList.contains(packageName)) {
+                        // Not a TV input package.
+                        return;
+                    }
+                }
+
+                ArrayList<ContentProviderOperation> operations =
+                        new ArrayList<ContentProviderOperation>();
+
+                String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
+                String[] selectionArgs = { packageName };
+
+                operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
+                        .withSelection(selection, selectionArgs).build());
+                operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
+                        .withSelection(selection, selectionArgs).build());
+                operations.add(ContentProviderOperation
+                        .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
+                        .withSelection(selection, selectionArgs).build());
+
+                ContentProviderResult[] results = null;
+                try {
+                    results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+                } catch (RemoteException | OperationApplicationException e) {
+                    Slog.e(TAG, "error in applyBatch" + e);
+                }
+
+                if (DEBUG) {
+                    Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
+                            + ")");
+                    Slog.d(TAG, "results=" + results);
+                }
+            }
         };
-        monitor.register(mContext, Looper.getMainLooper(), UserHandle.ALL, true);
+        monitor.register(mContext, null, UserHandle.ALL, true);
 
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -153,6 +195,7 @@
     private void buildTvInputListLocked(int userId) {
         UserState userState = getUserStateLocked(userId);
         userState.inputMap.clear();
+        userState.packageList.clear();
 
         if (DEBUG) Slog.d(TAG, "buildTvInputList");
         PackageManager pm = mContext.getPackageManager();
@@ -170,6 +213,10 @@
                 TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
                 if (DEBUG) Slog.d(TAG, "add " + info.getId());
                 userState.inputMap.put(info.getId(), info);
+                userState.packageList.add(si.packageName);
+
+                // Reconnect the service if existing input is updated.
+                updateServiceConnectionLocked(info.getId(), userId);
             } catch (IOException | XmlPullParserException e) {
                 Slog.e(TAG, "Can't load TV input " + si.name, e);
             }
@@ -309,6 +356,9 @@
 
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
                     userState.inputMap.get(inputId).getComponent());
+            // Binding service may fail if the service is updating.
+            // In that case, the connection will be revived in buildTvInputListLocked called by
+            // onSomePackagesChanged.
             serviceState.mBound = mContext.bindServiceAsUser(
                     i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
         } else if (serviceState.mService != null && isStateEmpty) {
@@ -356,7 +406,7 @@
                     if (session == null) {
                         removeSessionStateLocked(sessionToken, userId);
                         sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
-                                null, null, sessionState.mSeq, userId);
+                                null, null, sessionState.mSeq);
                     } else {
                         try {
                             session.asBinder().linkToDeath(sessionState, 0);
@@ -372,7 +422,7 @@
                         clientState.mSessionTokens.add(sessionState.mSessionToken);
 
                         sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
-                                sessionToken, channels[0], sessionState.mSeq, userId);
+                                sessionToken, channels[0], sessionState.mSeq);
                     }
                     channels[0].dispose();
                 }
@@ -457,13 +507,13 @@
             Slog.e(TAG, "error in createSession", e);
             removeSessionStateLocked(sessionToken, userId);
             sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
-                    sessionState.mSeq, userId);
+                    sessionState.mSeq);
         }
         channels[1].dispose();
     }
 
     private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
-            IBinder sessionToken, InputChannel channel, int seq, int userId) {
+            IBinder sessionToken, InputChannel channel, int seq) {
         try {
             client.onSessionCreated(inputId, sessionToken, channel, seq);
         } catch (RemoteException exception) {
@@ -595,7 +645,9 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            return false;
+            // STOPSHIP: Redesign the API around the availability change. For now, the service
+            // will be always available.
+            return true;
         }
 
         @Override
@@ -680,7 +732,7 @@
                     }
                     // Send a null token immediately while reconnecting.
                     if (serviceState.mReconnecting == true) {
-                        sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId);
+                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
                         return;
                     }
 
@@ -792,7 +844,10 @@
                         }
 
                         // Create a log entry and fill it later.
+                        String packageName = userState.inputMap.get(sessionState.mInputId)
+                                .getServiceInfo().packageName;
                         ContentValues values = new ContentValues();
+                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
                         values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
                                 currentTime);
                         values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
@@ -939,6 +994,9 @@
         // A mapping from the TV input id to its TvInputInfo.
         private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
 
+        // A list of all TV input packages.
+        private final Set<String> packageList = new HashSet<String>();
+
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> clientStateMap =
                 new HashMap<IBinder, ClientState>();
@@ -1103,8 +1161,7 @@
                         if (sessionState.mSession == null) {
                             removeSessionStateLocked(sessionToken, sessionState.mUserId);
                             sendSessionTokenToClientLocked(sessionState.mClient,
-                                    sessionState.mInputId, null, null, sessionState.mSeq,
-                                    sessionState.mUserId);
+                                    sessionState.mInputId, null, null, sessionState.mSeq);
                         }
                     }
 
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..d05d0c7 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,39 +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) {
-                        unaccountedSpace.op(boundsInScreen, unaccountedSpace,
-                                Region.Op.DIFFERENCE);
+                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
                         continue;
                     }
 
@@ -956,33 +972,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 +996,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 +1077,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 +1144,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 +1216,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 +1227,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/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a354c45..dfb1200 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -154,7 +154,8 @@
             for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                 final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows;
                 for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
-                    if (windows.get(winNdx).mWinAnimator.isAnimating()) {
+                    final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator;
+                    if (winAnimator.isAnimating() && !winAnimator.isDummyAnimation()) {
                         return true;
                     }
                 }
@@ -236,9 +237,22 @@
 
     void detachDisplay() {
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
+
+        boolean doAnotherLayoutPass = false;
         for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            mService.tmpRemoveTaskWindowsLocked(mTasks.get(taskNdx));
+            final AppTokenList appWindowTokens = mTasks.get(taskNdx).mAppTokens;
+            for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) {
+                final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows;
+                for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) {
+                    mService.removeWindowInnerLocked(null, appWindows.get(winNdx));
+                    doAnotherLayoutPass = true;
+                }
+            }
         }
+        if (doAnotherLayoutPass) {
+            mService.requestTraversalLocked();
+        }
+
         mAnimationBackgroundSurface.destroySurface();
         mAnimationBackgroundSurface = null;
         mDimLayer.destroySurface();
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 008d2fc..f1d0585d 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE;
 import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED;
@@ -227,7 +228,7 @@
                     continue;
                 }
                 final WindowStateAnimator winAnimator = win.mWinAnimator;
-                if (mPolicy.doesForceHide(win.mAttrs)) {
+                if ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                     if (!winAnimator.mAnimating) {
                         // Create a new animation to delay until keyguard is gone on its own.
                         winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f);
@@ -268,7 +269,7 @@
                     }
                 }
 
-                if (mPolicy.doesForceHide(win.mAttrs)) {
+                if (mPolicy.isForceHiding(win.mAttrs)) {
                     if (!wasAnimating && nowAnimating) {
                         if (WindowManagerService.DEBUG_ANIM ||
                                 WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
@@ -390,7 +391,7 @@
                     final WindowStateAnimator winAnimator = unForceHiding.get(i);
                     winAnimator.setAnimation(a);
                     winAnimator.mAnimationIsEntrance = true;
-                    if (startKeyguardExit) {
+                    if (startKeyguardExit && mKeyguardGoingAway) {
                         // Do one time only.
                         mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(),
                                 a.getDuration());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1db8b55..1d85723 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;
 
@@ -412,7 +411,7 @@
     /**
      * And the callback to make when they've all been drawn.
      */
-    IRemoteCallback mWaitingForDrawnCallback;
+    Runnable mWaitingForDrawnCallback;
 
     /**
      * Windows that have called relayout() while we were running animations,
@@ -433,7 +432,6 @@
     final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
-    CircularDisplayMask mCircularDisplayMask;
     FocusedStackFrame mFocusedStackFrame;
 
     int mFocusedStackLayer;
@@ -768,6 +766,8 @@
         mDisplaySettings = new DisplaySettings(context);
         mDisplaySettings.readSettingsLocked();
 
+        LocalServices.addService(WindowManagerPolicy.class, mPolicy);
+
         mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG));
 
         mFxSession = new SurfaceSession();
@@ -781,7 +781,6 @@
 
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
-        mPowerManagerInternal.setPolicy(mPolicy); // TODO: register as local service instead
         mPowerManagerInternal.registerLowPowerModeObserver(
                 new PowerManagerInternal.LowPowerModeListener() {
             @Override
@@ -846,8 +845,6 @@
         } finally {
             SurfaceControl.closeTransaction();
         }
-
-        showCircularDisplayMaskIfNeeded();
     }
 
     public InputMonitor getInputMonitor() {
@@ -1487,10 +1484,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);
@@ -2528,7 +2530,7 @@
         Binder.restoreCallingIdentity(origId);
     }
 
-    private void removeWindowInnerLocked(Session session, WindowState win) {
+    void removeWindowInnerLocked(Session session, WindowState win) {
         if (win.mRemoved) {
             // Nothing to do.
             return;
@@ -2810,14 +2812,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 +5575,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 +5832,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 +7181,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) {
@@ -7592,7 +7561,7 @@
                 }
 
                 case WAITING_FOR_DRAWN_TIMEOUT: {
-                    IRemoteCallback callback = null;
+                    Runnable callback = null;
                     synchronized (mWindowMap) {
                         Slog.w(TAG, "Timeout waiting for drawn: undrawn=" + mWaitingForDrawn);
                         mWaitingForDrawn.clear();
@@ -7600,10 +7569,7 @@
                         mWaitingForDrawnCallback = null;
                     }
                     if (callback != null) {
-                        try {
-                            callback.sendResult(null);
-                        } catch (RemoteException e) {
-                        }
+                        callback.run();
                     }
                     break;
                 }
@@ -7613,11 +7579,6 @@
                     break;
                 }
 
-                case SHOW_DISPLAY_MASK: {
-                    showCircularMask();
-                    break;
-                }
-
                 case DO_ANIMATION_CALLBACK: {
                     try {
                         ((IRemoteCallback)msg.obj).sendResult(null);
@@ -7662,16 +7623,13 @@
                     }
                     break;
                 case ALL_WINDOWS_DRAWN: {
-                    IRemoteCallback callback;
+                    Runnable callback;
                     synchronized (mWindowMap) {
                         callback = mWaitingForDrawnCallback;
                         mWaitingForDrawnCallback = null;
                     }
                     if (callback != null) {
-                        try {
-                            callback.sendResult(null);
-                        } catch (RemoteException e) {
-                        }
+                        callback.run();
                     }
                 }
                 case NEW_ANIMATOR_SCALE: {
@@ -8479,7 +8437,7 @@
                     }
                     win.mLayoutNeeded = false;
                     win.prelayout();
-                    mPolicy.layoutWindowLw(win, win.mAttrs, null);
+                    mPolicy.layoutWindowLw(win, null);
                     win.mLayoutSeq = seq;
                     if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame="
                             + win.mFrame + " mContainingFrame="
@@ -8531,7 +8489,7 @@
                     }
                     win.mLayoutNeeded = false;
                     win.prelayout();
-                    mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
+                    mPolicy.layoutWindowLw(win, win.mAttachedWindow);
                     win.mLayoutSeq = seq;
                     if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame="
                             + win.mFrame + " mContainingFrame="
@@ -9133,9 +9091,6 @@
             if (mStrictModeFlash != null) {
                 mStrictModeFlash.positionSurface(defaultDw, defaultDh);
             }
-            if (mCircularDisplayMask != null) {
-                mCircularDisplayMask.positionSurface(defaultDw, defaultDh, mRotation);
-            }
 
             boolean focusDisplayed = false;
 
@@ -9266,6 +9221,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) {
@@ -9587,8 +9549,9 @@
             }
         }
 
-        if (mInnerFields.mOrientationChangeComplete && !defaultDisplay.layoutNeeded
-                && !mInnerFields.mUpdateRotation) {
+        if (mWaitingForDrawnCallback != null ||
+                (mInnerFields.mOrientationChangeComplete && !defaultDisplay.layoutNeeded &&
+                        !mInnerFields.mUpdateRotation)) {
             checkDrawnWindowsLocked();
         }
 
@@ -9904,7 +9867,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();
             }
 
@@ -10351,6 +10316,10 @@
         mPolicy.lockNow(options);
     }
 
+    public void showRecentApps() {
+        mPolicy.showRecentApps();
+    }
+
     @Override
     public boolean isSafeModeEnabled() {
         return mSafeMode;
@@ -11171,24 +11140,31 @@
             }
         }
 
-        public void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout) {
+        public void waitForAllWindowsDrawn(Runnable callback, long timeout) {
             synchronized (mWindowMap) {
                 mWaitingForDrawnCallback = callback;
                 final WindowList windows = getDefaultWindowListLocked();
                 for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                     final WindowState win = windows.get(winNdx);
-                    if (win.mHasSurface) {
-                        win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                    if (win.mHasSurface && win.isWinVisibleLw()) {
+                        if (!win.mIsWallpaper) {
+                            // Don't force wallpaper to redraw.
+                            win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                        }
                         // Force add to mResizingWindows.
                         win.mLastContentInsets.set(-1, -1, -1, -1);
                         mWaitingForDrawn.add(win);
                     }
                 }
                 requestTraversalLocked();
-                mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
-                mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
             }
-            checkDrawnWindowsLocked();
+            mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+            if (mWaitingForDrawn.isEmpty()) {
+                callback.run();
+            } else {
+                mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
+                checkDrawnWindowsLocked();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4a80e3e..7003c8c 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;
@@ -727,8 +728,11 @@
     }
 
     public DisplayContent getDisplayContent() {
-        return mAppToken == null || mNotOnAppsDisplay ?
-                mDisplayContent : getStack().getDisplayContent();
+        if (mAppToken == null || mNotOnAppsDisplay) {
+            return mDisplayContent;
+        }
+        final TaskStack stack = getStack();
+        return stack == null ? mDisplayContent : stack.getDisplayContent();
     }
 
     public int getDisplayId() {
@@ -1359,6 +1363,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..3696e24 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, ...) \
@@ -60,12 +61,9 @@
         jobject canvas, jint width, jint height) {
 
     SkBitmap* bitmap = new SkBitmap;
-    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
-    bitmap->allocPixels();
+    bitmap->allocN32Pixels(width, height);
     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 +72,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 +241,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..0f919d1 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -57,12 +57,21 @@
     int getVersion();
     // Get vendor id used for vendor command.
     uint32_t getVendorId();
+    // Get Port information on all the HDMI ports.
+    jobjectArray getPortInfos();
+    // 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;
     }
 
 private:
+    static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
     static void onReceived(const hdmi_event_t* event, void* arg);
 
     hdmi_cec_device_t* mDevice;
@@ -203,11 +212,11 @@
 }
 
 int HdmiCecController::getPhysicalAddress() {
-    uint16_t physicalAddress = 0xFFFF;
-    if (mDevice->get_physical_address(mDevice, &physicalAddress) == 0) {
-        return physicalAddress;
+    uint16_t addr;
+    if (!mDevice->get_physical_address(mDevice, &addr)) {
+        return addr;
     }
-    return -1;
+    return INVALID_PHYSICAL_ADDRESS;
 }
 
 int HdmiCecController::getVersion() {
@@ -222,6 +231,47 @@
     return vendorId;
 }
 
+jobjectArray HdmiCecController::getPortInfos() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo");
+    if (hdmiPortInfo == NULL) {
+        return NULL;
+    }
+    jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V");
+    if (ctor == NULL) {
+        return NULL;
+    }
+    hdmi_port_info* ports;
+    int numPorts;
+    mDevice->get_port_info(mDevice, &ports, &numPorts);
+    jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL);
+
+    // MHL support field will be obtained from MHL HAL. Leave it to false.
+    jboolean mhlSupported = (jboolean) 0;
+    for (int i = 0; i < numPorts; ++i) {
+        hdmi_port_info* info = &ports[i];
+        jboolean cecSupported = (jboolean) info->cec_supported;
+        jboolean arcSupported = (jboolean) info->arc_supported;
+        jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_num, info->type,
+                info->physical_address, cecSupported, mhlSupported, arcSupported);
+        env->SetObjectArrayElement(res, i, infoObj);
+    }
+    return res;
+}
+
+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) {
@@ -291,41 +341,53 @@
     return controller->sendMessage(message);
 }
 
-static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz,
-        jlong controllerPtr, jint logicalAddress) {
-    HdmiCecController* controller =
-            reinterpret_cast<HdmiCecController*>(controllerPtr);
-    return controller->addLogicalAddress(
-            static_cast<cec_logical_address_t>(logicalAddress));
+static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr,
+        jint logicalAddress) {
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
+    return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress));
 }
 
-static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz,
-        jlong controllerPtr) {
-    HdmiCecController* controller =
-            reinterpret_cast<HdmiCecController*>(controllerPtr);
+static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
     controller->clearLogicaladdress();
 }
 
-static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz,
-        jlong controllerPtr) {
-    HdmiCecController* controller =
-            reinterpret_cast<HdmiCecController*>(controllerPtr);
+static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
     return controller->getPhysicalAddress();
 }
 
-static jint nativeGetVersion(JNIEnv* env, jclass clazz,
-        jlong controllerPtr) {
-    HdmiCecController* controller =
-            reinterpret_cast<HdmiCecController*>(controllerPtr);
+static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) {
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
     return controller->getVersion();
 }
 
 static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
-    HdmiCecController* controller =
-            reinterpret_cast<HdmiCecController*>(controllerPtr);
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
     return controller->getVendorId();
 }
 
+static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) {
+    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
+    return controller->getPortInfos();
+}
+
+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,13 +399,18 @@
     { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
     { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
     { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
+    { "nativeGetPortInfos",
+      "(J)[Landroid/hardware/hdmi/HdmiPortInfo;",
+      (void *) nativeGetPortInfos },
+    { "nativeSetOption", "(JII)V", (void *) nativeSetOption },
+    { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel },
+    { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
 };
 
 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
 
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
-            NELEM(sMethods));
+    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
     LOG_FATAL_IF(res < 0, "Unable to register native methods.");
     return 0;
 }
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/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index afe629d..7b8e6fd 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -54,6 +54,18 @@
     jmethodID build;
 } gTvStreamConfigBuilderClassInfo;
 
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+    jmethodID deviceId;
+    jmethodID type;
+    jmethodID hdmiPortId;
+    jmethodID audioType;
+    jmethodID audioAddress;
+    jmethodID build;
+} gTvInputHardwareInfoBuilderClassInfo;
+
 ////////////////////////////////////////////////////////////////////////////////
 
 class JTvInputHal {
@@ -63,7 +75,6 @@
     static JTvInputHal* createInstance(JNIEnv* env, jobject thiz);
 
     int setSurface(int deviceId, int streamId, const sp<Surface>& surface);
-    void getStreamConfigs(int deviceId, jobjectArray* array);
     const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs);
 
 private:
@@ -209,7 +220,6 @@
     return configs;
 }
 
-
 // static
 void JTvInputHal::notify(
         tv_input_device_t* dev, tv_input_event_t* event, void* data) {
@@ -232,11 +242,36 @@
 void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     mConnections.add(info.device_id, Connection());
+
+    jobject builder = env->NewObject(
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            gTvInputHardwareInfoBuilderClassInfo.constructor);
+    env->CallObjectMethod(
+            builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id);
+    env->CallObjectMethod(
+            builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
+    if (info.type == TV_INPUT_TYPE_HDMI) {
+        env->CallObjectMethod(
+                builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id);
+    }
+    env->CallObjectMethod(
+            builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type);
+    if (info.audio_type != AUDIO_DEVICE_NONE) {
+        jstring audioAddress = env->NewStringUTF(info.audio_address);
+        env->CallObjectMethod(
+                builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
+        env->DeleteLocalRef(audioAddress);
+    }
+
+    jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
+
     env->CallVoidMethod(
             mThiz,
             gTvInputHalClassInfo.deviceAvailable,
-            info.device_id,
-            info.type);
+            infoObject);
+
+    env->DeleteLocalRef(builder);
+    env->DeleteLocalRef(infoObject);
 }
 
 void JTvInputHal::onDeviceUnavailable(int deviceId) {
@@ -339,7 +374,8 @@
     FIND_CLASS(clazz, "com/android/server/tv/TvInputHal");
 
     GET_METHOD_ID(
-            gTvInputHalClassInfo.deviceAvailable, clazz, "deviceAvailableFromNative", "(II)V");
+            gTvInputHalClassInfo.deviceAvailable, clazz,
+            "deviceAvailableFromNative", "(Landroid/media/tv/TvInputHardwareInfo;)V");
     GET_METHOD_ID(
             gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V");
     GET_METHOD_ID(
@@ -382,6 +418,40 @@
             gTvStreamConfigBuilderClassInfo.clazz,
             "build", "()Landroid/media/tv/TvStreamConfig;");
 
+    FIND_CLASS(gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "android/media/tv/TvInputHardwareInfo$Builder");
+    gTvInputHardwareInfoBuilderClassInfo.clazz =
+            jclass(env->NewGlobalRef(gTvInputHardwareInfoBuilderClassInfo.clazz));
+
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.constructor,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "<init>", "()V");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.deviceId,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "deviceId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.type,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "type", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.hdmiPortId,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "hdmiPortId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.audioType,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "audioType", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "audioAddress", "(Ljava/lang/String;)Landroid/media/tv/TvInputHardwareInfo$Builder;");
+    GET_METHOD_ID(
+            gTvInputHardwareInfoBuilderClassInfo.build,
+            gTvInputHardwareInfoBuilderClassInfo.clazz,
+            "build", "()Landroid/media/tv/TvInputHardwareInfo;");
+
     return 0;
 }
 
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..893a891 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;
@@ -188,7 +195,9 @@
                 = new ArrayList<ActiveAdmin>();
 
         // This is the list of component allowed to start lock task mode.
-        final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>();
+        final List<String> mLockTaskPackages = new ArrayList<String>();
+
+        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++) {
@@ -985,10 +1014,10 @@
                 out.endTag(null, "active-password");
             }
 
-            for (int i=0; i<policy.mLockTaskComponents.size(); i++) {
-                ComponentName component = policy.mLockTaskComponents.get(i);
+            for (int i=0; i<policy.mLockTaskPackages.size(); i++) {
+                String component = policy.mLockTaskPackages.get(i);
                 out.startTag(null, LOCK_TASK_COMPONENTS_XML);
-                out.attribute(null, "name", component.flattenToString());
+                out.attribute(null, "name", component);
                 out.endTag(null, LOCK_TASK_COMPONENTS_XML);
             }
 
@@ -1039,9 +1068,16 @@
                 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();
+            policy.mLockTaskPackages.clear();
             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -1095,9 +1131,7 @@
                             parser.getAttributeValue(null, "nonletter"));
                     XmlUtils.skipCurrentTag(parser);
                 } else if (LOCK_TASK_COMPONENTS_XML.equals(tag)) {
-                    policy.mLockTaskComponents.add
-                        (ComponentName.unflattenFromString
-                         (parser.getAttributeValue(null, "name")));
+                    policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
                     XmlUtils.skipCurrentTag(parser);
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
@@ -1212,6 +1246,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 +1267,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();
@@ -1261,7 +1322,7 @@
     private void manageMonitoringCertificateNotification(Intent intent) {
         final NotificationManager notificationManager = getNotificationManager();
 
-        final boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
+        final boolean hasCert = !(new TrustedCertificateStore().userAliases().isEmpty());
         if (! hasCert) {
             if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
                 for (UserInfo user : mUserManager.getUsers()) {
@@ -1321,6 +1382,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 +1423,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);
             }
@@ -2188,9 +2255,12 @@
         try {
             LockPatternUtils utils = new LockPatternUtils(mContext);
             utils.saveLockPassword(password, quality, false, userHandle);
+            boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
+            if (requireEntry) {
+                utils.requireCredentialEntry(UserHandle.USER_ALL);
+            }
             synchronized (this) {
-                int newOwner = (flags&DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY)
-                        != 0 ? callingUid : -1;
+                int newOwner = requireEntry ? callingUid : -1;
                 if (policy.mPasswordOwner != newOwner) {
                     policy.mPasswordOwner = newOwner;
                     saveSettingsLocked(userHandle);
@@ -2303,6 +2373,7 @@
             getIPowerManager().goToSleep(SystemClock.uptimeMillis(),
                     PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
             // Ensure the device is locked
+            new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
             getWindowManager().lockNow(null);
         } catch (RemoteException e) {
         } finally {
@@ -2315,13 +2386,19 @@
         return !"".equals(state);
     }
 
-    public boolean installCaCert(byte[] certBuffer) throws RemoteException {
-        mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
-        KeyChainConnection keyChainConnection = null;
+    public boolean installCaCert(ComponentName who, byte[] certBuffer) throws RemoteException {
+        if (who == null) {
+            mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+        } else {
+            synchronized (this) {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            }
+        }
+
         byte[] pemCert;
         try {
             X509Certificate cert = parseCert(certBuffer);
-            pemCert =  Credentials.convertToPem(cert);
+            pemCert = Credentials.convertToPem(cert);
         } catch (CertificateException ce) {
             Log.e(LOG_TAG, "Problem converting cert", ce);
             return false;
@@ -2329,20 +2406,24 @@
             Log.e(LOG_TAG, "Problem reading cert", ioe);
             return false;
         }
+
+        final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+        final long id = Binder.clearCallingIdentity();
         try {
-            keyChainConnection = KeyChain.bind(mContext);
+            final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
             try {
                 keyChainConnection.getService().installCaCertificate(pemCert);
                 return true;
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
             } finally {
-                if (keyChainConnection != null) {
-                    keyChainConnection.close();
-                    keyChainConnection = null;
-                }
+                keyChainConnection.close();
             }
         } catch (InterruptedException e1) {
             Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
             Thread.currentThread().interrupt();
+        } finally {
+            Binder.restoreCallingIdentity(id);
         }
         return false;
     }
@@ -2354,34 +2435,31 @@
                 certBuffer));
     }
 
-    public void uninstallCaCert(final byte[] certBuffer) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
-        TrustedCertificateStore certStore = new TrustedCertificateStore();
-        String alias = null;
-        try {
-            X509Certificate cert = parseCert(certBuffer);
-            alias = certStore.getCertificateAlias(cert);
-        } catch (CertificateException ce) {
-            Log.e(LOG_TAG, "Problem creating X509Certificate", ce);
-            return;
-        } catch (IOException ioe) {
-            Log.e(LOG_TAG, "Problem reading certificate", ioe);
-            return;
+    public void uninstallCaCert(ComponentName who, String alias) {
+        if (who == null) {
+            mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+        } else {
+            synchronized (this) {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            }
         }
+
+        final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+        final long id = Binder.clearCallingIdentity();
         try {
-            KeyChainConnection keyChainConnection = KeyChain.bind(mContext);
-            IKeyChainService service = keyChainConnection.getService();
+            final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
             try {
-                service.deleteCaCertificate(alias);
+                keyChainConnection.getService().deleteCaCertificate(alias);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
             } finally {
                 keyChainConnection.close();
-                keyChainConnection = null;
             }
         } catch (InterruptedException ie) {
             Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
             Thread.currentThread().interrupt();
+        } finally {
+            Binder.restoreCallingIdentity(id);
         }
     }
 
@@ -2680,6 +2758,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 +3133,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 +3231,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 +3427,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,13 +3464,13 @@
             IPackageManager pm = AppGlobals.getPackageManager();
             long id = Binder.clearCallingIdentity();
             try {
-                if ((flags & DevicePolicyManager.FLAG_TO_PRIMARY_USER) != 0) {
-                    pm.addForwardingIntentFilter(filter, true /*removable*/, callingUserId,
-                            UserHandle.USER_OWNER);
+                if ((flags & DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED) != 0) {
+                    pm.addCrossProfileIntentFilter(filter, callingUserId, UserHandle.USER_OWNER,
+                            PackageManager.SET_BY_PROFILE_OWNER);
                 }
-                if ((flags & DevicePolicyManager.FLAG_TO_MANAGED_PROFILE) != 0) {
-                    pm.addForwardingIntentFilter(filter, true /*removable*/, UserHandle.USER_OWNER,
-                            callingUserId);
+                if ((flags & DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT) != 0) {
+                    pm.addCrossProfileIntentFilter(filter, UserHandle.USER_OWNER, callingUserId,
+                            PackageManager.SET_BY_PROFILE_OWNER);
                 }
             } catch (RemoteException re) {
                 // Shouldn't happen
@@ -3330,7 +3480,7 @@
         }
     }
 
-    public void clearForwardingIntentFilters(ComponentName who) {
+    public void clearCrossProfileIntentFilters(ComponentName who) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
             if (who == null) {
@@ -3340,8 +3490,10 @@
             IPackageManager pm = AppGlobals.getPackageManager();
             long id = Binder.clearCallingIdentity();
             try {
-                pm.clearForwardingIntentFilters(callingUserId);
-                pm.clearForwardingIntentFilters(UserHandle.USER_OWNER);
+                pm.clearCrossProfileIntentFilters(callingUserId);
+                // If we want to support multiple managed profiles, we will have to only remove
+                // those that have callingUserId as their target.
+                pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER);
             } catch (RemoteException re) {
                 // Shouldn't happen
             } finally {
@@ -3372,6 +3524,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 +3696,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) {
@@ -3657,38 +3734,38 @@
     }
 
     /**
-     * Sets which componets may enter lock task mode.
+     * Sets which packages may enter lock task mode.
      *
-     * This function can only be called by the device owner or the profile owner.
+     * This function can only be called by the device owner.
      * @param components The list of components allowed to enter lock task mode.
      */
-    public void setLockTaskComponents(ComponentName[] components) throws SecurityException {
+    public void setLockTaskPackages(String[] packages) throws SecurityException {
         // Get the package names of the caller.
         int uid = Binder.getCallingUid();
         String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
 
-        // Check whether any of the package name is the device owner or the profile owner.
-        for (int i=0; i<packageNames.length; i++) {
-            String packageName = packageNames[i];
-            int userHandle = UserHandle.getUserId(uid);
-            String profileOwnerPackage = getProfileOwner(userHandle);
-            if (isDeviceOwner(packageName) ||
-                (profileOwnerPackage != null && profileOwnerPackage.equals(packageName))) {
+        synchronized (this) {
+            // Check whether any of the package name is the device owner.
+            for (int i=0; i<packageNames.length; i++) {
+                String packageName = packageNames[i];
+                int userHandle = UserHandle.getUserId(uid);
+                if (isDeviceOwner(packageName)) {
 
-                // If a package name is the device owner or the profile owner,
-                // we update the component list.
-                DevicePolicyData policy = getUserData(userHandle);
-                policy.mLockTaskComponents.clear();
-                if (components != null) {
-                    for (int j=0; j<components.length; j++) {
-                        ComponentName component = components[j];
-                        policy.mLockTaskComponents.add(component);
+                    // If a package name is the device owner,
+                    // we update the component list.
+                    DevicePolicyData policy = getUserData(userHandle);
+                    policy.mLockTaskPackages.clear();
+                    if (packages != null) {
+                        for (int j = 0; j < packages.length; j++) {
+                            String pkg = packages[j];
+                            policy.mLockTaskPackages.add(pkg);
+                        }
                     }
-                }
 
-                // Store the settings persistently.
-                saveSettingsLocked(userHandle);
-                return;
+                    // Store the settings persistently.
+                    saveSettingsLocked(userHandle);
+                    return;
+                }
             }
         }
         throw new SecurityException();
@@ -3697,36 +3774,61 @@
     /**
      * This function returns the list of components allowed to start the task lock mode.
      */
-    public ComponentName[] getLockTaskComponents() {
-        int userHandle = UserHandle.USER_OWNER;
-        DevicePolicyData policy = getUserData(userHandle);
-        ComponentName[] tempArray = policy.mLockTaskComponents.toArray(new ComponentName[0]);
-        return tempArray;
+    public String[] getLockTaskPackages() {
+        synchronized (this) {
+            int userHandle = UserHandle.USER_OWNER;
+            DevicePolicyData policy = getUserData(userHandle);
+            return policy.mLockTaskPackages.toArray(new String[0]);
+        }
     }
 
     /**
-     * This function lets the caller know whether the given component is allowed to start the
+     * This function lets the caller know whether the given package is allowed to start the
      * lock task mode.
-     * @param component The component to check
+     * @param pkg The package to check
      */
-    public boolean isLockTaskPermitted(ComponentName component) {
+    public boolean isLockTaskPermitted(String pkg) {
         // Get current user's devicepolicy
         int uid = Binder.getCallingUid();
         int userHandle = UserHandle.getUserId(uid);
         DevicePolicyData policy = getUserData(userHandle);
-        for (int i=0; i<policy.mLockTaskComponents.size(); i++) {
-            ComponentName lockTaskComponent = policy.mLockTaskComponents.get(i);
+        synchronized (this) {
+            for (int i = 0; i < policy.mLockTaskPackages.size(); i++) {
+                String lockTaskPackage = policy.mLockTaskPackages.get(i);
 
-            // If the given component equals one of the component stored our device-owner-set
-            // list, we allow this component to start the lock task mode.
-            if (lockTaskComponent.getPackageName().equals(component.getPackageName())) {
-                return true;
+                // If the given package equals one of the packages stored our list,
+                // we allow this package to start lock task mode.
+                if (lockTaskPackage.equals(pkg)) {
+                    return true;
+                }
             }
         }
         return false;
     }
 
     @Override
+    public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
+        }
+        synchronized (this) {
+            final DevicePolicyData policy = getUserData(userHandle);
+            Bundle adminExtras = new Bundle();
+            adminExtras.putBoolean(DeviceAdminReceiver.EXTRA_LOCK_TASK_ENTERING, isEnabled);
+            adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
+            for (ActiveAdmin admin : policy.mAdminList) {
+                boolean ownsDevice = isDeviceOwner(admin.info.getPackageName());
+                boolean ownsProfile = (getProfileOwner(userHandle) != null
+                        && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
+                if (ownsDevice || ownsProfile) {
+                    sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_CHANGED,
+                            adminExtras, null);
+                }
+            }
+        }
+    }
+
+    @Override
     public void setGlobalSetting(ComponentName who, String setting, String value) {
         final ContentResolver contentResolver = mContext.getContentResolver();
 
@@ -3764,4 +3866,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..0d05c5f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -63,6 +63,7 @@
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
+import com.android.server.fingerprint.FingerprintService;
 import com.android.server.hdmi.HdmiControlService;
 import com.android.server.input.InputManagerService;
 import com.android.server.job.JobSchedulerService;
@@ -81,6 +82,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 +128,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 =
@@ -145,8 +147,13 @@
     private PowerManagerService mPowerManagerService;
     private ActivityManagerService mActivityManagerService;
     private DisplayManagerService mDisplayManagerService;
+    private PackageManagerService mPackageManagerService;
+    private PackageManager mPackageManager;
     private ContentResolver mContentResolver;
 
+    private boolean mOnlyCore;
+    private boolean mFirstBoot;
+
     /**
      * Called to initialize native system services.
      */
@@ -160,6 +167,7 @@
     }
 
     public SystemServer() {
+        // Check for factory test mode.
         mFactoryTestMode = FactoryTest.getMode();
     }
 
@@ -242,7 +250,7 @@
             startBootstrapServices();
             startCoreServices();
             startOtherServices();
-        } catch (RuntimeException ex) {
+        } catch (Throwable ex) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting system services", ex);
             throw ex;
@@ -286,36 +294,84 @@
         mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
     }
 
+    /**
+     * Starts the small tangle of critical services that are needed to get
+     * the system off the ground.  These services have complex mutual dependencies
+     * which is why we initialize them all in one place here.  Unless your service
+     * is also entwined in these dependencies, it should be initialized in one of
+     * the other functions.
+     */
     private void startBootstrapServices() {
         // Wait for installd to finish starting up so that it has a chance to
         // create critical directories such as /data/user with the appropriate
         // permissions.  We need this to complete before we initialize other services.
         mInstaller = mSystemServiceManager.startService(Installer.class);
 
-        // Power manager needs to be started early because other services need it.
-        // TODO: The conversion to the new pattern is incomplete.  We need to switch
-        // the power manager's dependencies over then we can use boot phases to arrange
-        // initialization order and remove the mPowerManagerService field.
-        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
-
         // Activity manager runs the show.
         mActivityManagerService = mSystemServiceManager.startService(
                 ActivityManagerService.Lifecycle.class).getService();
         mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
-    }
 
-    private void startCoreServices() {
+        // Power manager needs to be started early because other services need it.
+        // Native daemons may be watching for it to be registered so it must be ready
+        // to handle incoming binder calls immediately (including being able to verify
+        // the permissions for those calls).
+        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
+
+        // Now that the power manager has been started, let the activity manager
+        // initialize power management features.
+        mActivityManagerService.initPowerManagement();
+
         // Display manager is needed to provide display metrics before package manager
         // starts up.
         mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);
+
+        // We need the default display before we can initialize the package manager.
+        mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        // Only run "core" apps if we're encrypting the device.
+        String cryptState = SystemProperties.get("vold.decrypt");
+        if (ENCRYPTING_STATE.equals(cryptState)) {
+            Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
+            mOnlyCore = true;
+        } else if (ENCRYPTED_STATE.equals(cryptState)) {
+            Slog.w(TAG, "Device encrypted - only parsing core apps");
+            mOnlyCore = true;
+        }
+
+        // Start the package manager.
+        Slog.i(TAG, "Package Manager");
+        mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
+                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
+        mFirstBoot = mPackageManagerService.isFirstBoot();
+        mPackageManager = mSystemContext.getPackageManager();
+
+        // Initialize attribute cache used to cache resources from packages.
+        AttributeCache.init(mSystemContext);
+
+        // Set up the Application instance for the system process and get started.
+        mActivityManagerService.setSystemProcess();
     }
 
+    /**
+     * Starts some essential services that are not tangled up in the bootstrap process.
+     */
+    private void startCoreServices() {
+        // Manages LEDs and display backlight.
+        mSystemServiceManager.startService(LightsService.class);
+
+        // Tracks the battery level.  Requires LightService.
+        mSystemServiceManager.startService(BatteryService.class);
+    }
+
+    /**
+     * Starts a miscellaneous grab bag of stuff that has yet to be refactored
+     * and organized.
+     */
     private void startOtherServices() {
         final Context context = mSystemContext;
         AccountManagerService accountManager = null;
         ContentService contentService = null;
-        LightsManager lights = null;
-        BatteryService battery = null;
         VibratorService vibrator = null;
         IAlarmManager alarm = null;
         MountService mountService = null;
@@ -325,10 +381,8 @@
         ConnectivityService connectivity = null;
         NetworkScoreService networkScore = null;
         NsdService serviceDiscovery= null;
-        IPackageManager pm = null;
         WindowManagerService wm = null;
         BluetoothManagerService bluetooth = null;
-        DockObserver dock = null;
         UsbService usb = null;
         SerialService serial = null;
         RecognitionManagerService recognition = null;
@@ -339,8 +393,6 @@
         ConsumerIrService consumerIr = null;
         AudioService audioService = null;
 
-        boolean onlyCore = false;
-        boolean firstBoot = false;
         boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
         boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
         boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -352,38 +404,12 @@
         boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
 
         try {
-            Slog.i(TAG, "Telephony Registry");
-            telephonyRegistry = new TelephonyRegistry(context);
-            ServiceManager.addService("telephony.registry", telephonyRegistry);
-
             Slog.i(TAG, "Scheduling Policy");
             ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
 
-            AttributeCache.init(context);
-
-            // We need the default display before we can initialize the package manager.
-            mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-            Slog.i(TAG, "Package Manager");
-            // Only run "core" apps if we're encrypting the device.
-            String cryptState = SystemProperties.get("vold.decrypt");
-            if (ENCRYPTING_STATE.equals(cryptState)) {
-                Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
-                onlyCore = true;
-            } else if (ENCRYPTED_STATE.equals(cryptState)) {
-                Slog.w(TAG, "Device encrypted - only parsing core apps");
-                onlyCore = true;
-            }
-
-            pm = PackageManagerService.main(context, mInstaller,
-                    mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
-                    onlyCore);
-            try {
-                firstBoot = pm.isFirstBoot();
-            } catch (RemoteException e) {
-            }
-
-            mActivityManagerService.setSystemProcess();
+            Slog.i(TAG, "Telephony Registry");
+            telephonyRegistry = new TelephonyRegistry(context);
+            ServiceManager.addService("telephony.registry", telephonyRegistry);
 
             Slog.i(TAG, "Entropy Mixer");
             ServiceManager.addService("entropy", new EntropyMixer(context));
@@ -411,24 +437,10 @@
             Slog.i(TAG, "System Content Providers");
             mActivityManagerService.installSystemProviders();
 
-            mSystemServiceManager.startService(LightsService.class);
-            lights = LocalServices.getService(LightsManager.class);
-
-            Slog.i(TAG, "Battery Service");
-            battery = new BatteryService(context, lights);
-            ServiceManager.addService("battery", battery);
-
             Slog.i(TAG, "Vibrator Service");
             vibrator = new VibratorService(context);
             ServiceManager.addService("vibrator", vibrator);
 
-            // TODO: use boot phase
-            // only initialize the power service after we have started the
-            // lights service, content providers and the battery service.
-            mPowerManagerService.init(lights, battery,
-                    BatteryStatsService.getService(),
-                    mActivityManagerService.getAppOpsService());
-
             Slog.i(TAG, "Consumer IR Service");
             consumerIr = new ConsumerIrService(context);
             ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr);
@@ -447,7 +459,7 @@
             Slog.i(TAG, "Window Manager");
             wm = WindowManagerService.main(context, inputManager,
                     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
-                    !firstBoot, onlyCore);
+                    !mFirstBoot, mOnlyCore);
             ServiceManager.addService(Context.WINDOW_SERVICE, wm);
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
 
@@ -521,7 +533,7 @@
         }
 
         try {
-            pm.performBootDexOpt();
+            mPackageManagerService.performBootDexOpt();
         } catch (Throwable e) {
             reportWtf("performing boot dexopt", e);
         }
@@ -559,13 +571,9 @@
                     reportWtf("starting LockSettingsService service", e);
                 }
 
-                try {
-                    // Always start the Device Policy Manager, so that the API is compatible with
-                    // API8.
-                    mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
-                } catch (Throwable e) {
-                    reportWtf("starting DevicePolicyService", e);
-                }
+                // Always start the Device Policy Manager, so that the API is compatible with
+                // API8.
+                mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
             }
 
             if (!disableSystemUI) {
@@ -636,39 +644,17 @@
                     reportWtf("starting NetworkPolicy Service", e);
                 }
 
-                try {
-                    mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
-                } catch (Throwable e) {
-                    reportWtf("starting Wi-Fi P2pService", e);
-                }
+                mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
 
-                try {
-                    mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                } catch (Throwable e) {
-                    reportWtf("starting Wi-Fi Service", e);
-                }
+                mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS);
 
-                try {
-                    mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS);
-                } catch (Throwable e) {
-                    reportWtf("starting Wi-Fi PasspointService", e);
-                }
+                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
 
-                try {
-                    Slog.i(TAG, "Wi-Fi Scanning Service");
-                    mSystemServiceManager.startService(
+                mSystemServiceManager.startService(
                             "com.android.server.wifi.WifiScanningService");
 
-                } catch (Throwable e) {
-                    reportWtf("starting Wi-Fi Scanning Service", e);
-                }
-
                 if (!isEmulator) {
-                    try {
-                        mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
-                    } catch (Throwable e) {
-                        reportWtf("starting Ethernet Service", e);
-                    }
+                    mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
                 } else {
                     // Don't start the Ethernet service on the emulator because
                     // it interferes with qemu's SLIRP emulation, which uses
@@ -712,7 +698,7 @@
              * AppWidget Provider. Make sure MountService is completely started
              * first before continuing.
              */
-            if (mountService != null && !onlyCore) {
+            if (mountService != null && !mOnlyCore) {
                 mountService.waitForAsecScan();
             }
 
@@ -795,13 +781,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) {
@@ -816,14 +796,11 @@
             }
 
             if (!disableNonCoreServices) {
-                try {
-                    if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST) ||
-                            pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
-                        // Manage USB host and device support
-                        mSystemServiceManager.startService(USB_SERVICE_CLASS);
-                    }
-                } catch (Throwable e) {
-                    reportWtf("starting UsbService", e);
+                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
+                        || mPackageManager.hasSystemFeature(
+                                PackageManager.FEATURE_USB_ACCESSORY)) {
+                    // Manage USB host and device support
+                    mSystemServiceManager.startService(USB_SERVICE_CLASS);
                 }
 
                 try {
@@ -843,20 +820,12 @@
             mSystemServiceManager.startService(JobSchedulerService.class);
 
             if (!disableNonCoreServices) {
-                try {
-                    if (pm.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
-                        mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
-                    }
-                } catch (Throwable e) {
-                    Slog.e(TAG, "Failure starting Backup Service", e);
+                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
+                    mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
                 }
 
-                try {
-                    if (pm.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
-                        mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
-                    }
-                } catch (Throwable e) {
-                    reportWtf("starting AppWidget Service", e);
+                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
+                    mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
                 }
 
                 try {
@@ -866,13 +835,8 @@
                     reportWtf("starting Recognition Service", e);
                 }
 
-                try {
-                    if (pm.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
-                        Slog.i(TAG, "Voice Recognition Service");
-                        mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
-                    }
-                } catch (Throwable e) {
-                    reportWtf("starting Voice Recognition Service", e);
+                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
+                    mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
                 }
             }
 
@@ -938,39 +902,17 @@
                 }
             }
 
-            try {
-                Slog.i(TAG, "IdleMaintenanceService");
-                new IdleMaintenanceService(context, battery);
-            } catch (Throwable e) {
-                reportWtf("starting IdleMaintenanceService", e);
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
+                mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
             }
 
-            try {
-                if (pm.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
-                    mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
-                }
-            } catch (Throwable e) {
-                reportWtf("starting Print Service", e);
-            }
+            mSystemServiceManager.startService(RestrictionsManagerService.class);
 
-            try {
-                mSystemServiceManager.startService(MediaSessionService.class);
-            } catch (Throwable e) {
-                reportWtf("starting MediaSessionService", e);
-            }
+            mSystemServiceManager.startService(MediaSessionService.class);
 
-            try {
-                mSystemServiceManager.startService(HdmiControlService.class);
-            } catch (Throwable e) {
-                reportWtf("starting HdmiControlService", e);
-            }
+            mSystemServiceManager.startService(HdmiControlService.class);
 
-            try {
-                Slog.i(TAG, "TvInputManagerService");
-                mSystemServiceManager.startService(TvInputManagerService.class);
-            } catch (Throwable e) {
-                reportWtf("starting TvInputManagerService", e);
-            }
+            mSystemServiceManager.startService(TvInputManagerService.class);
 
             if (!disableNonCoreServices) {
                 try {
@@ -981,28 +923,20 @@
                     reportWtf("starting MediaRouterService", e);
                 }
 
-                try {
-                    Slog.i(TAG, "Trust Manager");
-                    mSystemServiceManager.startService(TrustManagerService.class);
-                } catch (Throwable e) {
-                    Slog.e(TAG, "Failure starting TrustManagerService", e);
-                }
+                mSystemServiceManager.startService(TrustManagerService.class);
+
+                mSystemServiceManager.startService(FingerprintService.class);
 
                 try {
                     Slog.i(TAG, "BackgroundDexOptService");
-                    new BackgroundDexOptService(context);
+                    BackgroundDexOptService.schedule(context);
                 } catch (Throwable e) {
                     reportWtf("starting BackgroundDexOptService", e);
                 }
+
             }
 
-            try {
-                Slog.i(TAG, "LauncherAppsService");
-                LauncherAppsService las = new LauncherAppsService(context);
-                ServiceManager.addService(Context.LAUNCHER_APPS_SERVICE, las);
-            } catch (Throwable t) {
-                reportWtf("starting LauncherAppsService", t);
-            }
+            mSystemServiceManager.startService(LauncherAppsService.class);
         }
 
         // Before things start rolling, be sure we have decided whether
@@ -1059,33 +993,31 @@
 
         try {
             // TODO: use boot phase
-            mPowerManagerService.systemReady();
+            mPowerManagerService.systemReady(mActivityManagerService.getAppOpsService());
         } catch (Throwable e) {
             reportWtf("making Power Manager Service ready", e);
         }
 
         try {
-            pm.systemReady();
+            mPackageManagerService.systemReady();
         } catch (Throwable e) {
             reportWtf("making Package Manager Service ready", e);
         }
 
         try {
             // TODO: use boot phase and communicate these flags some other way
-            mDisplayManagerService.systemReady(safeMode, onlyCore);
+            mDisplayManagerService.systemReady(safeMode, mOnlyCore);
         } catch (Throwable e) {
             reportWtf("making Display Manager Service ready", e);
         }
 
         // These are needed to propagate to the runnable below.
         final MountService mountServiceF = mountService;
-        final BatteryService batteryF = battery;
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkStatsService networkStatsF = networkStats;
         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;
@@ -1129,11 +1061,6 @@
                     reportWtf("making Mount Service ready", e);
                 }
                 try {
-                    if (batteryF != null) batteryF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Battery Service ready", e);
-                }
-                try {
                     if (networkScoreF != null) networkScoreF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Network Score Service ready", e);
@@ -1159,11 +1086,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/CallCameraCapabilities.aidl b/telecomm/java/android/telecomm/CallCameraCapabilities.aidl
new file mode 100644
index 0000000..25b6106
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallCameraCapabilities.aidl
@@ -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 android.telecomm;
+
+parcelable CallCameraCapabilities;
diff --git a/telecomm/java/android/telecomm/CallCameraCapabilities.java b/telecomm/java/android/telecomm/CallCameraCapabilities.java
new file mode 100644
index 0000000..87a411a
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallCameraCapabilities.java
@@ -0,0 +1,113 @@
+/*
+ * 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.telecomm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the camera capabilities important to a Video Telephony provider.
+ * TODO: Add camera capabilities as required.
+ */
+public final class CallCameraCapabilities implements Parcelable {
+
+    /**
+     * Whether the camera supports zoom.
+     */
+    private final boolean mZoomSupported;
+
+    /**
+     * The maximum zoom supported by the camera.
+     */
+    private final float mMaxZoom;
+
+    /**
+     * Create a call camera capabilities instance.
+     *
+     * @param zoomSupported True when camera supports zoom.
+     * @param maxZoom Maximum zoom supported by camera.
+     */
+    public CallCameraCapabilities(boolean zoomSupported, float maxZoom) {
+        mZoomSupported = zoomSupported;
+        mMaxZoom = maxZoom;
+    }
+
+    /**
+     * Responsible for creating CallCameraCapabilities objects from deserialized Parcels.
+     **/
+    public static final Parcelable.Creator<CallCameraCapabilities> CREATOR =
+            new Parcelable.Creator<CallCameraCapabilities> () {
+                /**
+                 * Creates a CallCameraCapabilities instances from a parcel.
+                 *
+                 * @param source The parcel.
+                 * @return The CallCameraCapabilities.
+                 */
+                @Override
+                public CallCameraCapabilities createFromParcel(Parcel source) {
+                    boolean supportsZoom = source.readByte() != 0;
+                    float maxZoom = source.readFloat();
+
+                    return new CallCameraCapabilities(supportsZoom, maxZoom);
+                }
+
+                @Override
+                public CallCameraCapabilities[] newArray(int size) {
+                    return new CallCameraCapabilities[size];
+                }
+            };
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     *
+     * @return a bitmask indicating the set of special object types marshalled
+     * by the Parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (isZoomSupported() ? 1 : 0));
+        dest.writeFloat(getMaxZoom());
+
+    }
+
+    /**
+     * Whether the camera supports zoom.
+     */
+    public boolean isZoomSupported() {
+        return mZoomSupported;
+    }
+
+    /**
+     * The maximum zoom supported by the camera.
+     */
+    public float getMaxZoom() {
+        return mMaxZoom;
+    }
+}
diff --git a/telecomm/java/android/telecomm/CallInfo.java b/telecomm/java/android/telecomm/CallInfo.java
index cb7f2dc..17efed5 100644
--- a/telecomm/java/android/telecomm/CallInfo.java
+++ b/telecomm/java/android/telecomm/CallInfo.java
@@ -24,7 +24,7 @@
 import java.util.Date;
 
 /**
- * A parcelable holder class of Call information data. This class is intended for transfering call
+ * A parcelable holder class of Call information data. This class is intended for transferring call
  * information from Telecomm to call services and thus is read-only.
  * TODO(santoscordon): Need final public-facing comments in this file.
  */
@@ -52,8 +52,12 @@
     private final GatewayInfo mGatewayInfo;
 
     /**
-     * Additional information that can be persisted. For example, extra handoff information can
-     * attached to a call using {@link CallServiceSelectorAdapter#setHandoffInfo(String,Uri,Bundle).
+     * Subscription information for the call.
+     */
+    private final Subscription mSubscription;
+
+    /**
+     * Additional information that can be persisted.
      */
     private final Bundle mExtras;
 
@@ -61,7 +65,7 @@
     private final CallServiceDescriptor mCurrentCallServiceDescriptor;
 
     public CallInfo(String id, CallState state, Uri handle) {
-        this(id, state, handle, null, Bundle.EMPTY, null);
+        this(id, state, handle, null, null, Bundle.EMPTY, null);
     }
 
     /**
@@ -71,6 +75,7 @@
      * @param state The state of the call.
      * @param handle The handle to the other party in this call.
      * @param gatewayInfo Gateway information pertaining to this call.
+     * @param subscription Subscription information pertaining to this call.
      * @param extras Additional information that can be persisted.
      * @param currentCallServiceDescriptor The descriptor for the call service currently routing
      *         this call.
@@ -82,12 +87,14 @@
             CallState state,
             Uri handle,
             GatewayInfo gatewayInfo,
+            Subscription subscription,
             Bundle extras,
             CallServiceDescriptor currentCallServiceDescriptor) {
         mId = id;
         mState = state;
         mHandle = handle;
         mGatewayInfo = gatewayInfo;
+        mSubscription = subscription;
         mExtras = extras;
         mCurrentCallServiceDescriptor = currentCallServiceDescriptor;
     }
@@ -120,6 +127,10 @@
         return mGatewayInfo;
     }
 
+    public Subscription getSubscription() {
+        return mSubscription;
+    }
+
     public Bundle getExtras() {
         return mExtras;
     }
@@ -138,16 +149,13 @@
             CallState state = CallState.valueOf(source.readString());
             Uri handle = Uri.CREATOR.createFromParcel(source);
 
-            boolean gatewayInfoPresent = source.readByte() != 0;
-            GatewayInfo gatewayInfo = null;
-            if (gatewayInfoPresent) {
-                gatewayInfo = GatewayInfo.CREATOR.createFromParcel(source);
-            }
+            GatewayInfo gatewayInfo = readProviderInfoIfExists(source, GatewayInfo.CREATOR);
+            Subscription subscription = readProviderInfoIfExists(source, Subscription.CREATOR);
 
             ClassLoader classLoader = CallInfo.class.getClassLoader();
             Bundle extras = source.readParcelable(classLoader);
             CallServiceDescriptor descriptor = source.readParcelable(classLoader);
-            return new CallInfo(id, state, handle, gatewayInfo, extras, descriptor);
+            return new CallInfo(id, state, handle, gatewayInfo, subscription, extras, descriptor);
         }
 
         @Override
@@ -173,14 +181,35 @@
         destination.writeString(mState.name());
         mHandle.writeToParcel(destination, 0);
 
-        if (mGatewayInfo != null) {
-            destination.writeByte((byte) 1);
-            mGatewayInfo.writeToParcel(destination, 0);
-        } else {
-            destination.writeByte((byte) 0);
-        }
+        writeProviderInfoIfExists(destination, mGatewayInfo);
+        writeProviderInfoIfExists(destination, mSubscription);
 
         destination.writeParcelable(mExtras, 0);
         destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
     }
+
+    /**
+     * Helper function to write provider information (either GatewayInfo or Subscription) to
+     * parcel. Will write a false byte if the information does not exist.
+     */
+    private void writeProviderInfoIfExists(Parcel destination, Parcelable provider) {
+        if (provider != null) {
+            destination.writeByte((byte) 1);
+            provider.writeToParcel(destination, 0);
+        } else {
+            destination.writeByte((byte) 0);
+        }
+    }
+
+    /**
+     * Helper function to read provider information (either GatewayInfo or Subscription) from
+     * parcel.
+     */
+    private static <T> T readProviderInfoIfExists(Parcel source,
+            Parcelable.Creator<T> creator) {
+        if (source.readByte() != 0) {
+            return creator.createFromParcel(source);
+        }
+        return null;
+    }
 }
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index 0b5981c..12ba1ff 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,8 +27,6 @@
 import com.android.internal.telecomm.ICallService;
 import com.android.internal.telecomm.ICallServiceAdapter;
 
-import java.util.List;
-
 /**
  * Base implementation of CallService which can be used to provide calls for the system
  * in-call UI. CallService is a one-way service from the framework's CallsManager to any app
@@ -49,21 +47,20 @@
 public abstract class CallService extends Service {
 
     private static final int MSG_SET_CALL_SERVICE_ADAPTER = 1;
-    private static final int MSG_IS_COMPATIBLE_WITH = 2;
-    private static final int MSG_CALL = 3;
-    private static final int MSG_ABORT = 4;
-    private static final int MSG_SET_INCOMING_CALL_ID = 5;
-    private static final int MSG_ANSWER = 6;
-    private static final int MSG_REJECT = 7;
-    private static final int MSG_DISCONNECT = 8;
-    private static final int MSG_HOLD = 9;
-    private static final int MSG_UNHOLD = 10;
-    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_SPLIT_FROM_CONFERENCE = 15;
-    private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
+    private static final int MSG_CALL = 2;
+    private static final int MSG_ABORT = 3;
+    private static final int MSG_SET_INCOMING_CALL_ID = 4;
+    private static final int MSG_ANSWER = 5;
+    private static final int MSG_REJECT = 6;
+    private static final int MSG_DISCONNECT = 7;
+    private static final int MSG_HOLD = 8;
+    private static final int MSG_UNHOLD = 9;
+    private static final int MSG_ON_AUDIO_STATE_CHANGED = 10;
+    private static final int MSG_PLAY_DTMF_TONE = 11;
+    private static final int MSG_STOP_DTMF_TONE = 12;
+    private static final int MSG_CONFERENCE = 13;
+    private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
+    private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
 
     /**
      * Default Handler used to consolidate binder method calls onto a single thread.
@@ -73,12 +70,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CALL_SERVICE_ADAPTER:
-                    mAdapter = new CallServiceAdapter((ICallServiceAdapter) msg.obj);
+                    mAdapter.addAdapter((ICallServiceAdapter) msg.obj);
                     onAdapterAttached(mAdapter);
                     break;
-                case MSG_IS_COMPATIBLE_WITH:
-                    isCompatibleWith((CallInfo) msg.obj);
-                    break;
                 case MSG_CALL:
                     call((CallInfo) msg.obj);
                     break;
@@ -170,11 +164,6 @@
         }
 
         @Override
-        public void isCompatibleWith(CallInfo callInfo) {
-            mMessageHandler.obtainMessage(MSG_IS_COMPATIBLE_WITH, callInfo).sendToTarget();
-        }
-
-        @Override
         public void call(CallInfo callInfo) {
             mMessageHandler.obtainMessage(MSG_CALL, callInfo).sendToTarget();
         }
@@ -268,7 +257,7 @@
      */
     private final CallServiceBinder mBinder = new CallServiceBinder();
 
-    private CallServiceAdapter mAdapter = null;
+    private CallServiceAdapter mAdapter = new CallServiceAdapter();
 
     /** {@inheritDoc} */
     @Override
@@ -300,21 +289,10 @@
     }
 
     /**
-     * Determines if the CallService can place the specified call. Response is sent via
-     * {@link CallServiceAdapter#setIsCompatibleWith}. When responding, the correct call ID must be
-     * specified.  Only used in the context of outgoing calls and call switching (handoff).
-     *
-     * @param callInfo The details of the relevant call.
-     */
-    public abstract void isCompatibleWith(CallInfo callInfo);
-
-    /**
      * Attempts to call the relevant party using the specified call's handle, be it a phone number,
      * SIP address, or some other kind of user ID.  Note that the set of handle types is
      * dynamically extensible since call providers should be able to implement arbitrary
-     * handle-calling systems.  See {@link #isCompatibleWith}. It is expected that the
-     * call service respond via {@link CallServiceAdapter#handleSuccessfulOutgoingCall(String)}
-     * if it can successfully make the call.  Only used in the context of outgoing calls.
+     * handle-calling systems.
      *
      * @param callInfo The details of the relevant call.
      */
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index ce89321..8412e80 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -16,54 +16,87 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 
+import com.android.internal.telecomm.ICallService;
 import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.ICallVideoProvider;
+import com.android.internal.telecomm.RemoteServiceCallback;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Provides methods for ICallService implementations to interact with the system phone app.
  * TODO(santoscordon): Need final public-facing comments in this file.
+ * TODO(santoscordon): Rename this to CallServiceAdapterDemultiplexer (or something).
  */
-public final class CallServiceAdapter {
-    private final ICallServiceAdapter mAdapter;
+public final class CallServiceAdapter implements DeathRecipient {
+    private final Set<ICallServiceAdapter> mAdapters = new HashSet<>();
 
     /**
-     * {@hide}
+     * @hide
      */
-    public CallServiceAdapter(ICallServiceAdapter adapter) {
-        mAdapter = adapter;
+    public CallServiceAdapter() {
     }
 
     /**
-     * Receives confirmation of a call service's ability to place a call. This method is used in
-     * response to {@link CallService#isCompatibleWith}.
-     *
-     * @param callId The identifier of the call for which compatibility is being received. This ID
-     *     should correspond to the ID given as part of the call information in
-     *     {@link CallService#isCompatibleWith}.
-     * @param isCompatible True if the call service can place the call.
+     * @hide
      */
-    public void setIsCompatibleWith(String callId, boolean isCompatible) {
-        try {
-            mAdapter.setIsCompatibleWith(callId, isCompatible);
-        } catch (RemoteException e) {
+    public void addAdapter(ICallServiceAdapter adapter) {
+        if (mAdapters.add(adapter)) {
+            try {
+                adapter.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                mAdapters.remove(adapter);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void removeAdapter(ICallServiceAdapter adapter) {
+        if (mAdapters.remove(adapter)) {
+            adapter.asBinder().unlinkToDeath(this, 0);
+        }
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void binderDied() {
+        ICallServiceAdapter adapterToRemove = null;
+        for (ICallServiceAdapter adapter : mAdapters) {
+            if (!adapter.asBinder().isBinderAlive()) {
+                adapterToRemove = adapter;
+                break;
+            }
+        }
+
+        if (adapterToRemove != null) {
+            removeAdapter(adapterToRemove);
         }
     }
 
     /**
      * Provides Telecomm with the details of an incoming call. An invocation of this method must
-     * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon
-     * the invocation of this method, Telecomm will bring up the incoming-call interface where the
-     * user can elect to answer or reject a call.
+     * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon the
+     * invocation of this method, Telecomm will bring up the incoming-call interface where the user
+     * can elect to answer or reject a call.
      *
      * @param callInfo The details of the relevant call.
      */
     public void notifyIncomingCall(CallInfo callInfo) {
-        try {
-            mAdapter.notifyIncomingCall(callInfo);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.notifyIncomingCall(callInfo);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -75,9 +108,11 @@
      * @param callId The ID of the outgoing call.
      */
     public void handleSuccessfulOutgoingCall(String callId) {
-        try {
-            mAdapter.handleSuccessfulOutgoingCall(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleSuccessfulOutgoingCall(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -92,9 +127,25 @@
             ConnectionRequest request,
             int errorCode,
             String errorMsg) {
-        try {
-            mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Tells Telecomm to cancel the call.
+     *
+     * @param callId The ID of the outgoing call.
+     */
+    public void cancelOutgoingCall(String callId) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.cancelOutgoingCall(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -105,9 +156,11 @@
      * @param callId The unique ID of the call whose state is changing to active.
      */
     public void setActive(String callId) {
-        try {
-            mAdapter.setActive(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setActive(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -117,9 +170,11 @@
      * @param callId The unique ID of the call whose state is changing to ringing.
      */
     public void setRinging(String callId) {
-        try {
-            mAdapter.setRinging(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRinging(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -129,9 +184,11 @@
      * @param callId The unique ID of the call whose state is changing to dialing.
      */
     public void setDialing(String callId) {
-        try {
-            mAdapter.setDialing(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDialing(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -140,13 +197,15 @@
      *
      * @param callId The unique ID of the call whose state is changing to disconnected.
      * @param disconnectCause The reason for the disconnection, any of
-     *         {@link android.telephony.DisconnectCause}.
+     *            {@link android.telephony.DisconnectCause}.
      * @param disconnectMessage Optional call-service-provided message about the disconnect.
      */
     public void setDisconnected(String callId, int disconnectCause, String disconnectMessage) {
-        try {
-            mAdapter.setDisconnected(callId, disconnectCause, disconnectMessage);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDisconnected(callId, disconnectCause, disconnectMessage);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -156,9 +215,11 @@
      * @param callId - The unique ID of the call whose state is changing to be on hold.
      */
     public void setOnHold(String callId) {
-        try {
-            mAdapter.setOnHold(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setOnHold(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -169,9 +230,11 @@
      * @param ringback Whether Telecomm should start playing a ringback tone.
      */
     public void setRequestingRingback(String callId, boolean ringback) {
-        try {
-            mAdapter.setRequestingRingback(callId, ringback);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRequestingRingback(callId, ringback);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -183,9 +246,11 @@
      * @hide
      */
     public void setCanConference(String callId, boolean canConference) {
-        try {
-            mAdapter.setCanConference(callId, canConference);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCanConference(callId, canConference);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
@@ -195,13 +260,15 @@
      *
      * @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.
+     *            conferenced.
      * @hide
      */
     public void setIsConferenced(String callId, String conferenceCallId) {
-        try {
-            mAdapter.setIsConferenced(callId, conferenceCallId);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setIsConferenced(callId, conferenceCallId);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
@@ -213,16 +280,34 @@
      * @hide
      */
     public void removeCall(String callId) {
-        try {
-            mAdapter.removeCall(callId);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.removeCall(callId);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
     public void onPostDialWait(String callId, String remaining) {
-        try {
-            mAdapter.onPostDialWait(callId, remaining);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onPostDialWait(callId, remaining);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Instructs Telecomm to handoff the call to another call service.
+     *
+     * @param callId The identifier of the call to handoff.
+     */
+    public void handoffCall(String callId) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handoffCall(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -232,9 +317,41 @@
      * @param callId The unique ID of the conference call.
      */
     public void addConferenceCall(String callId) {
-        try {
-            mAdapter.addConferenceCall(callId, null);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.addConferenceCall(callId, null);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Retrieves a list of remote connection services usable to place calls.
+     * @hide
+     */
+    public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+        // Only supported when there is only one adapter.
+        if (mAdapters.size() == 1) {
+            try {
+                mAdapters.iterator().next().queryRemoteConnectionServices(callback);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Exception trying to query for remote CSs");
+            }
+        }
+    }
+
+    /**
+     * Sets the call video provider for a call.
+     *
+     * @param callId The unique ID of the call to set with the given call video provider.
+     * @param callVideoProvider The call video provider instance to set on the call.
+     */
+    public void setCallVideoProvider(String callId, CallVideoProvider callVideoProvider) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCallVideoProvider(callId, callVideoProvider.getInterface());
+            } catch (RemoteException e) {
+            }
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/CallServiceSelector.java b/telecomm/java/android/telecomm/CallServiceSelector.java
deleted file mode 100644
index c9c6ff6..0000000
--- a/telecomm/java/android/telecomm/CallServiceSelector.java
+++ /dev/null
@@ -1,152 +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 android.telecomm;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-
-import com.android.internal.os.SomeArgs;
-import com.android.internal.telecomm.ICallServiceSelector;
-import com.android.internal.telecomm.ICallServiceSelectorAdapter;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Allows for the organization of {@link CallService}s for outbound calls. Given a call and list of
- * {@link CallService} IDs, order the list in terms of priority and return it using
- * {@link #select(CallInfo, List)}.
- */
-public abstract class CallServiceSelector extends Service {
-    private static final int MSG_SET_CALL_SERVICE_SELECTOR_ADAPTER = 0;
-    private static final int MSG_SELECT = 1;
-
-    private final HashMap<String, CallInfo> mCalls = new HashMap<String, CallInfo>();
-
-    /** Handler to move client-bound method calls to the main thread. */
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SET_CALL_SERVICE_SELECTOR_ADAPTER:
-                    mAdapter = new CallServiceSelectorAdapter(
-                            (ICallServiceSelectorAdapter) msg.obj);
-                    onAdapterAttached(mAdapter);
-                    break;
-                case MSG_SELECT:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        select((CallInfo) args.arg1, (List<CallServiceDescriptor>) args.arg2);
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-            }
-        }
-    };
-
-    /** Manages the binder calls so that the implementor does not need to deal with it. */
-    private final class CallServiceSelectorBinder extends ICallServiceSelector.Stub {
-        @Override
-        public void setCallServiceSelectorAdapter(ICallServiceSelectorAdapter adapter) {
-            mHandler.obtainMessage(MSG_SET_CALL_SERVICE_SELECTOR_ADAPTER, adapter)
-                    .sendToTarget();
-        }
-
-        @Override
-        public void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callInfo;
-            args.arg2 = descriptors;
-            mHandler.obtainMessage(MSG_SELECT, args).sendToTarget();
-        }
-
-        @Override
-        public void onCallUpdated(CallInfo callInfo) {
-            mCalls.put(callInfo.getId(), callInfo);
-        }
-
-        @Override
-        public void onCallRemoved(String callId) {
-            mCalls.remove(callId);
-        }
-    }
-
-    private final CallServiceSelectorBinder mBinder;
-
-    private CallServiceSelectorAdapter mAdapter = null;
-
-    protected CallServiceSelector() {
-        mBinder = new CallServiceSelectorBinder();
-    }
-
-    @Override
-    public final IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    /**
-     * Returns a list of all calls managed by this selector.
-     */
-    protected final Collection<CallInfo> getCalls() {
-        return Collections.unmodifiableCollection(mCalls.values());
-    }
-
-    /**
-     * @return The attached {@link CallServiceSelectorAdapter} if attached, or null otherwise.
-     */
-    protected final CallServiceSelectorAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Cancel the outgoing call. Any subsequent calls to {@link #select(CallInfo, List)} will be
-     * ignored.
-     *
-     * @param callInfo The call to canceled.
-     */
-    protected final void cancelOutgoingCall(CallInfo callInfo) {
-        getAdapter().cancelOutgoingCall(callInfo.getId());
-    }
-
-    /**
-     * Lifecycle callback which is called when this {@link CallServiceSelector} has been attached
-     * to a {@link CallServiceSelectorAdapter}, indicating {@link #getAdapter()} is now safe to use.
-     *
-     * @param adapter The adapter now attached to this call service selector.
-     */
-    protected void onAdapterAttached(CallServiceSelectorAdapter adapter) {
-    }
-
-    /**
-     * Given a list of {@link CallServiceDescriptor}s, order them into a prioritized list and return
-     * them through
-     * {@link CallServiceSelectorAdapter#setSelectedCallServices(String,List)}.
-     *
-     * @param callInfo The call being placed using the {@link CallService}s.
-     * @param descriptors The descriptors of the available {@link CallService}s with which to place
-     *            the call.
-     */
-    protected abstract void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors);
-}
diff --git a/telecomm/java/android/telecomm/CallServiceSelectorAdapter.java b/telecomm/java/android/telecomm/CallServiceSelectorAdapter.java
deleted file mode 100644
index 4d2e8aa..0000000
--- a/telecomm/java/android/telecomm/CallServiceSelectorAdapter.java
+++ /dev/null
@@ -1,84 +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 android.telecomm;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.telecomm.CallServiceDescriptor;
-
-import com.android.internal.telecomm.ICallServiceSelectorAdapter;
-
-import java.util.List;
-
-/**
- * Provides methods for ICallServiceSelector implementations to interact with Telecomm.
- */
-public final class CallServiceSelectorAdapter {
-    private final ICallServiceSelectorAdapter mAdapter;
-
-    /**
-     * {@hide}
-     */
-    public CallServiceSelectorAdapter(ICallServiceSelectorAdapter adapter) {
-        mAdapter = adapter;
-    }
-
-    /**
-     * Records the sorted set of call services that are preferred by the corresponding
-     * call-service selector.
-     *
-     * @param callId The ID of the call to complete.
-     * @param selectedCallServiceDescriptors The prioritized list of preferred call-service
-     *        descriptors to use for completing the call.
-     */
-    public void setSelectedCallServices(
-            String callId,
-            List<CallServiceDescriptor> selectedCallServiceDescriptors) {
-        try {
-            mAdapter.setSelectedCallServices(callId, selectedCallServiceDescriptors);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Cancels the specified outgoing call.
-     *
-     * @param callId The ID of the call to cancel.
-     */
-    public void cancelOutgoingCall(String callId) {
-        try {
-            mAdapter.cancelOutgoingCall(callId);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Associates handoff information with an ongoing call. Calls can switch from one call service
-     * to another. Setting handle to a non-null value marks the call as switchable.
-     *
-     * @param callId The ID of the call to set handoff information for.
-     * @param handle The handle used to place the call when switching.
-     * @param extras Optional extra that's attached to the call.
-     */
-    public void setHandoffInfo(String callId, Uri handle, Bundle extras) {
-        try {
-            mAdapter.setHandoffInfo(callId, handle, extras);
-        } catch (RemoteException e) {
-        }
-    }
-}
diff --git a/telecomm/java/android/telecomm/CallVideoClient.java b/telecomm/java/android/telecomm/CallVideoClient.java
new file mode 100644
index 0000000..4b37136
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallVideoClient.java
@@ -0,0 +1,245 @@
+/*
+ * 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.telecomm;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecomm.ICallVideoClient;
+
+/**
+ * Base implementation of a CallVideoClient which communicates changes to video properties of a call
+ * from the framework to the current InCall-UI.
+ */
+public abstract class CallVideoClient {
+
+    /**
+     * Video is not being received (no protocol pause was issued).
+     */
+    public static final int SESSION_EVENT_RX_PAUSE = 1;
+
+    /**
+     * Video reception has resumed after a SESSION_EVENT_RX_PAUSE.
+     */
+    public static final int SESSION_EVENT_RX_RESUME = 2;
+
+    /**
+     * Video transmission has begun. This occurs after a negotiated start of video transmission
+     * when the underlying protocol has actually begun transmitting video to the remote party.
+     */
+    public static final int SESSION_EVENT_TX_START = 3;
+
+    /**
+     * Video transmission has stopped. This occur after a negotiated stop of video transmission when
+     * the underlying protocol has actually stopped transmitting video to the remote party.
+     */
+    public static final int SESSION_EVENT_TX_STOP = 4;
+
+    /**
+     * Session modify request was successful.
+     */
+    public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1;
+
+    /**
+     * Session modify request failed.
+     */
+    public static final int SESSION_MODIFY_REQUEST_FAIL = 2;
+
+    /**
+     * Session modify request ignored due to invalid parameters.
+     */
+    public static final int SESSION_MODIFY_REQUEST_INVALID = 3;
+
+    private static final int MSG_ON_RECEIVE_SESSION_MODIFY_REQUEST = 1;
+    private static final int MSG_ON_RECEIVE_SESSION_MODIFY_RESPONSE = 2;
+    private static final int MSG_ON_CALL_SESSION_EVENT = 3;
+    private static final int MSG_ON_UPDATED_PEER_DIMENSIONS = 4;
+    private static final int MSG_ON_UPDATE_CALL_DATA_USAGE = 5;
+    private static final int MSG_ON_CAMERA_CAPABILITIES_CHANGE = 6;
+
+    /** Default Handler used to consolidate binder method calls onto a single thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_RECEIVE_SESSION_MODIFY_REQUEST:
+                    onReceiveSessionModifyRequest((VideoCallProfile) msg.obj);
+                    break;
+                case MSG_ON_RECEIVE_SESSION_MODIFY_RESPONSE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        int status = (int) args.arg1;
+                        VideoCallProfile requestedProfile = (VideoCallProfile) args.arg2;
+                        VideoCallProfile responseProfile = (VideoCallProfile) args.arg3;
+
+                        onReceiveSessionModifyResponse(status, requestedProfile,
+                                responseProfile);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_CALL_SESSION_EVENT:
+                    onCallSessionEvent((int) msg.obj);
+                    break;
+                case MSG_ON_UPDATED_PEER_DIMENSIONS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        int width = (int) args.arg1;
+                        int height = (int) args.arg2;
+                        onUpdatedPeerDimensions(width, height);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_UPDATE_CALL_DATA_USAGE:
+                    onUpdateCallDataUsage(msg.arg1);
+                    break;
+                case MSG_ON_CAMERA_CAPABILITIES_CHANGE:
+                    onCameraCapabilitiesChange((CallCameraCapabilities) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Default ICallVideoClient implementation.
+     */
+    private final class CallVideoClientBinder extends ICallVideoClient.Stub {
+        @Override
+        public void onReceiveSessionModifyRequest(VideoCallProfile videoCallProfile) {
+            mHandler.obtainMessage(MSG_ON_RECEIVE_SESSION_MODIFY_REQUEST,
+                    videoCallProfile).sendToTarget();
+        }
+
+        @Override
+        public void onReceiveSessionModifyResponse(int status,
+                VideoCallProfile requestedProfile, VideoCallProfile responseProfile) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = status;
+            args.arg2 = requestedProfile;
+            args.arg3 = responseProfile;
+            mHandler.obtainMessage(MSG_ON_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget();
+        }
+
+        @Override
+        public void onCallSessionEvent(int event) {
+            mHandler.obtainMessage(MSG_ON_CALL_SESSION_EVENT, event).sendToTarget();
+        }
+
+        @Override
+        public void onUpdatedPeerDimensions(int width, int height) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = width;
+            args.arg2 = height;
+            mHandler.obtainMessage(MSG_ON_UPDATED_PEER_DIMENSIONS, args).sendToTarget();
+        }
+
+        @Override
+        public void onUpdateCallDataUsage(int dataUsage) {
+            mHandler.obtainMessage(MSG_ON_UPDATE_CALL_DATA_USAGE, dataUsage).sendToTarget();
+        }
+
+        @Override
+        public void onCameraCapabilitiesChange(CallCameraCapabilities cameraCapabilities) {
+            mHandler.obtainMessage(MSG_ON_CAMERA_CAPABILITIES_CHANGE,
+                    cameraCapabilities).sendToTarget();
+        }
+    }
+
+    private final CallVideoClientBinder mBinder;
+
+    protected CallVideoClient() {
+        mBinder = new CallVideoClientBinder();
+    }
+
+    /**
+     * Retrieves the binder.
+     *
+     * @return The binder.
+     * @hide
+     */
+    public final CallVideoClientBinder getBinder() {
+        return mBinder;
+    }
+
+    /**
+     * Called when an incoming request is received from a remote device to modify the current
+     * video call profile.  This could be, for example, a remote request to upgrade from an
+     * audio-only call to a video call.
+     *
+     * @param videoCallProfile The requested video call profile.
+     */
+    public abstract void onReceiveSessionModifyRequest(VideoCallProfile videoCallProfile);
+
+    /**
+     * Called when a response is received to a previously sent request to modify the video call
+     * profile.  For example, if a request was previously sent to the other party to upgrade from an
+     * audio-only call to a video call, the other party responds to indicate which profile
+     * changes were accepted.
+     *
+     * @param status Status of the session modify request.  Valid values are
+     *               {@link CallVideoClient#SESSION_MODIFY_REQUEST_SUCCESS},
+     *               {@link CallVideoClient#SESSION_MODIFY_REQUEST_FAIL},
+     *               {@link CallVideoClient#SESSION_MODIFY_REQUEST_INVALID}
+     * @param requestedProfile The original request which was sent to the remote device.
+     * @param responseProfile The actual profile changes made by the remote device.
+     */
+    public abstract void onReceiveSessionModifyResponse(int status,
+            VideoCallProfile requestedProfile, VideoCallProfile responseProfile);
+
+    /**
+     * Handles events related to the current session which the client may wish to handle.  These
+     * are separate from requested changes to the session due to the underlying protocol or
+     * connection.
+     * Valid values are: {@link CallVideoClient#SESSION_EVENT_RX_PAUSE},
+     * {@link CallVideoClient#SESSION_EVENT_RX_RESUME},
+     * {@link CallVideoClient#SESSION_EVENT_TX_START}, {@link CallVideoClient#SESSION_EVENT_TX_STOP}
+     *
+     * @param event The event.
+     */
+    public abstract void onCallSessionEvent(int event);
+
+    /**
+     * Handles a change to the video dimensions from the remote caller (peer).  This could happen
+     * if, for example, the peer changes orientation of their device.
+     *
+     * @param width  The updated peer video width.
+     * @param height The updated peer video height.
+     */
+    public abstract void onUpdatedPeerDimensions(int width, int height);
+
+    /**
+     * Handles an update to the total data used for the current session.
+     *
+     * @param dataUsage The updated data usage.
+     */
+    public abstract void onUpdateCallDataUsage(int dataUsage);
+
+    /**
+     * Handles a change in camera capabilities.
+     *
+     * @param callCameraCapabilities The changed camera capabilities.
+     */
+    public abstract void onCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities);
+}
+
diff --git a/telecomm/java/android/telecomm/CallVideoProvider.java b/telecomm/java/android/telecomm/CallVideoProvider.java
new file mode 100644
index 0000000..4f593b9
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallVideoProvider.java
@@ -0,0 +1,76 @@
+/*
+ * 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.telecomm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+
+import com.android.internal.telecomm.ICallVideoProvider;
+
+public abstract class CallVideoProvider {
+    private static final int MSG_SET_CAMERA = 1;
+
+    /**
+     * Default handler used to consolidate binder method calls onto a single thread.
+     */
+    private final class CallVideoProviderHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_CAMERA:
+                    setCamera((String) msg.obj);
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Default ICallVideoProvider implementation.
+     */
+    private final class CallVideoProviderBinder extends ICallVideoProvider.Stub {
+        public void setCamera(String cameraId) {
+            mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
+        }
+    }
+
+    private final CallVideoProviderHandler mMessageHandler = new CallVideoProviderHandler();
+    private final CallVideoProviderBinder mBinder;
+
+    protected CallVideoProvider() {
+        mBinder = new CallVideoProviderBinder();
+    }
+
+    /**
+     * Returns binder object which can be used across IPC methods.
+     * @hide
+     */
+    public final ICallVideoProvider getInterface() {
+        return mBinder;
+    }
+
+    /**
+     * Sets the camera to be used for video recording in a video call.
+     *
+     * @param cameraId The id of the camera.
+     */
+    public abstract void setCamera(String cameraId);
+}
diff --git a/telecomm/java/android/telecomm/CallVideoProviderWrapper.java b/telecomm/java/android/telecomm/CallVideoProviderWrapper.java
new file mode 100644
index 0000000..862adff
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallVideoProviderWrapper.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 android.telecomm;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telecomm.ICallVideoProvider;
+
+
+public class CallVideoProviderWrapper implements IBinder.DeathRecipient {
+    private final ICallVideoProvider mCallVideoProvider;
+
+    CallVideoProviderWrapper(ICallVideoProvider callVideoProvider) throws RemoteException {
+        mCallVideoProvider = callVideoProvider;
+        mCallVideoProvider.asBinder().linkToDeath(this, 0);
+    }
+
+    @Override
+    public void binderDied() {
+        mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
+    }
+
+    /**
+     * Sets the camera to be used for video recording in a video call, using the binder.
+     *
+     * @param cameraId The id of the camera.
+     */
+    public void setCamera(String cameraId) throws RemoteException {
+        mCallVideoProvider.setCamera(cameraId);
+    };
+}
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 164eeff..cf4e29a 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecomm.CallVideoProvider;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -40,6 +41,7 @@
         void onDestroyed(Connection c);
         void onConferenceCapableChanged(Connection c, boolean isConferenceCapable);
         void onParentConnectionChanged(Connection c, Connection parent);
+        void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider);
     }
 
     public static class ListenerBase implements Listener {
@@ -78,6 +80,10 @@
         /** ${inheritDoc} */
         @Override
         public void onParentConnectionChanged(Connection c, Connection parent) {}
+
+        /** {@inheritDoc} */
+        @Override
+        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
     }
 
     public final class State {
@@ -123,8 +129,6 @@
 
     /**
      * @return The state of this Connection.
-     *
-     * @hide
      */
     public final int getState() {
         return mState;
@@ -275,6 +279,8 @@
 
     /**
      * TODO(santoscordon): Needs updated documentation.
+     *
+     * @hide
      */
     public final void conference() {
         Log.d(this, "conference");
@@ -285,9 +291,14 @@
      * Inform this Connection that the state of its audio output has been changed externally.
      *
      * @param state The new audio state.
+     * @hide
      */
-    public void setAudioState(CallAudioState state) {
+    public final void setAudioState(CallAudioState state) {
         Log.d(this, "setAudioState %s", state);
+        mCallAudioState = state;
+        for (Listener l : mListeners) {
+            l.onAudioStateChanged(this, state);
+        }
         onSetAudioState(state);
     }
 
@@ -319,18 +330,21 @@
      * Returns whether this connection is requesting that the system play a ringback tone
      * on its behalf.
      */
-    public boolean isRequestingRingback() {
+    public final boolean isRequestingRingback() {
         return mRequestingRingback;
     }
 
     /**
      * Returns whether this connection is a conference connection (has child connections).
      */
-    public boolean isConferenceConnection() {
+    public final boolean isConferenceConnection() {
         return !mChildConnections.isEmpty();
     }
 
-    public void setParentConnection(Connection parentConnection) {
+    /**
+     * TODO(santoscordon): Needs documentation.
+     */
+    public final void setParentConnection(Connection parentConnection) {
         Log.d(this, "parenting %s to %s", this, parentConnection);
         if (mParentConnection != parentConnection) {
             if (mParentConnection != null) {
@@ -347,20 +361,27 @@
         }
     }
 
-    public Connection getParentConnection() {
+    public final Connection getParentConnection() {
         return mParentConnection;
     }
 
-    public List<Connection> getChildConnections() {
+    public final List<Connection> getChildConnections() {
         return mChildConnections;
     }
 
     /**
+     * Returns whether this connection is capable of being conferenced.
+     */
+    public final boolean isConferenceCapable() {
+        return mIsConferenceCapable;
+    }
+
+    /**
      * Sets the value of the {@link #getHandle()} property and notifies listeners.
      *
      * @param handle The new handle.
      */
-    protected void setHandle(Uri handle) {
+    public final void setHandle(Uri handle) {
         Log.d(this, "setHandle %s", handle);
         // TODO: Enforce super called
         mHandle = handle;
@@ -373,7 +394,7 @@
      * Sets state to active (e.g., an ongoing call where two or more parties can actively
      * communicate).
      */
-    protected void setActive() {
+    public final void setActive() {
         setRequestingRingback(false);
         setState(State.ACTIVE);
     }
@@ -381,25 +402,35 @@
     /**
      * Sets state to ringing (e.g., an inbound ringing call).
      */
-    protected void setRinging() {
+    public final void setRinging() {
         setState(State.RINGING);
     }
 
     /**
      * Sets state to dialing (e.g., dialing an outbound call).
      */
-    protected void setDialing() {
+    public final void setDialing() {
         setState(State.DIALING);
     }
 
     /**
      * Sets state to be on hold.
      */
-    protected void setOnHold() {
+    public final void setOnHold() {
         setState(State.HOLDING);
     }
 
     /**
+     * Sets the call video provider.
+     * @param callVideoProvider The call video provider.
+     */
+    public final void setCallVideoProvider(CallVideoProvider callVideoProvider) {
+        for (Listener l : mListeners) {
+            l.onSetCallVideoProvider(this, callVideoProvider);
+        }
+    }
+
+    /**
      * Sets state to disconnected. This will first notify listeners with an
      * {@link Listener#onStateChanged(Connection, int)} event, then will fire an
      * {@link Listener#onDisconnected(Connection, int, String)} event with additional
@@ -409,7 +440,7 @@
      *         {@link android.telephony.DisconnectCause}.
      * @param message Optional call-service-provided message about the disconnect.
      */
-    protected void setDisconnected(int cause, String message) {
+    public final void setDisconnected(int cause, String message) {
         setState(State.DISCONNECTED);
         Log.d(this, "Disconnected with cause %d message %s", cause, message);
         for (Listener l : mListeners) {
@@ -423,7 +454,7 @@
      *
      * @param ringback Whether the ringback tone is to be played.
      */
-    protected void setRequestingRingback(boolean ringback) {
+    public final void setRequestingRingback(boolean ringback) {
         if (mRequestingRingback != ringback) {
             mRequestingRingback = ringback;
             for (Listener l : mListeners) {
@@ -435,7 +466,7 @@
     /**
      * TODO(santoscordon): Needs documentation.
      */
-    protected void setIsConferenceCapable(boolean isConferenceCapable) {
+    public final void setIsConferenceCapable(boolean isConferenceCapable) {
         if (mIsConferenceCapable != isConferenceCapable) {
             mIsConferenceCapable = isConferenceCapable;
             for (Listener l : mListeners) {
@@ -447,7 +478,7 @@
     /**
      * TODO(santoscordon): Needs documentation.
      */
-    protected void setDestroyed() {
+    public final 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.
@@ -459,46 +490,32 @@
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
-     * has a new value.
-     *
-     * @param state The new call audio state.
-     */
-    protected void onSetAudioState(CallAudioState state) {
-        // TODO: Enforce super called
-        mCallAudioState = state;
-        for (Listener l : mListeners) {
-            l.onAudioStateChanged(this, state);
-        }
-    }
-
-    /**
      * Notifies this Connection and listeners of a change in the current signal levels
      * for the underlying data transport.
      *
      * @param details A {@link android.os.Bundle} containing details of the current level.
      */
-    protected void onSetSignal(Bundle details) {
-        // TODO: Enforce super called
+    public final void setSignal(Bundle details) {
         for (Listener l : mListeners) {
             l.onSignalChanged(this, details);
         }
     }
 
     /**
+     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
+     * has a new value.
+     *
+     * @param state The new call audio state.
+     */
+    protected void onSetAudioState(CallAudioState state) {}
+
+    /**
      * Notifies this Connection of an internal state change. This method is called before the
-     * state is actually changed. Overriding implementations must call
-     * {@code super.onSetState(state)}.
+     * state is actually changed.
      *
      * @param state The new state, a {@link Connection.State} member.
      */
-    protected void onSetState(int state) {
-        // TODO: Enforce super called
-        this.mState = state;
-        for (Listener l : mListeners) {
-            l.onStateChanged(this, state);
-        }
-    }
+    protected void onSetState(int state) {}
 
     /**
      * Notifies this Connection of a request to play a DTMF tone.
@@ -578,6 +595,10 @@
 
     private void setState(int state) {
         Log.d(this, "setState: %s", stateToString(state));
+        this.mState = state;
+        for (Listener l : mListeners) {
+            l.onStateChanged(this, state);
+        }
         onSetState(state);
     }
 }
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index bf5727b..61ac816 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -33,18 +33,29 @@
     private final String mCallId;
     private final Uri mHandle;
     private final Bundle mExtras;
+    private final Subscription mSubscription;
 
     public ConnectionRequest(Uri handle, Bundle extras) {
         this(null, handle, extras);
     }
 
     public ConnectionRequest(String callId, Uri handle, Bundle extras) {
+        this(null, callId, handle, extras);
+    }
+
+    public ConnectionRequest(Subscription subscription, String callId, Uri handle, Bundle extras) {
         mCallId = callId;
         mHandle = handle;
         mExtras = extras;
+        mSubscription = subscription;
     }
 
     /**
+     * The subscription which should be used to place the call.
+     */
+    public Subscription getSubscription() { return mSubscription; }
+
+    /**
      * An identifier for this call.
      */
     public String getCallId() { return mCallId; }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index d974509..4d525f20 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -16,19 +16,22 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Bundle;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
 import android.telephony.DisconnectCause;
 
-import android.os.SystemClock;
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.RemoteServiceCallback;
 
 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
@@ -42,6 +45,41 @@
     // Mappings from Connections to IDs as understood by the current CallService implementation
     private final Map<String, Connection> mConnectionById = new HashMap<>();
     private final Map<Connection, String> mIdByConnection = new HashMap<>();
+    private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private SimpleResponse<Uri, List<Subscription>> mSubscriptionLookupResponse;
+    private Uri mSubscriptionLookupHandle;
+    private boolean mAreSubscriptionsInitialized = false;
+
+    /**
+     * A callback for providing the resuilt of creating a connection.
+     */
+    public interface OutgoingCallResponse<CONNECTION> {
+        /**
+         * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
+         *
+         * @param request The original request.
+         * @param connection The connection.
+         */
+        void onSuccess(ConnectionRequest request, CONNECTION connection);
+
+        /**
+         * Tells Telecomm that an attempt to place the specified outgoing call failed.
+         *
+         * @param request The original request.
+         * @param code An integer code indicating the reason for failure.
+         * @param msg A message explaining the reason for failure.
+         */
+        void onFailure(ConnectionRequest request, int code, String msg);
+
+        /**
+         * Tells Telecomm to cancel the call.
+         *
+         * @param request The original request.
+         */
+        void onCancel(ConnectionRequest request);
+    }
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
@@ -117,31 +155,15 @@
             String parentId = parent == null ? null : mIdByConnection.get(parent);
             getAdapter().setIsConferenced(id, parentId);
         }
+
+        @Override
+        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {
+            String id = mIdByConnection.get(c);
+            getAdapter().setCallVideoProvider(id, callVideoProvider);
+        }
     };
 
     @Override
-    public final void isCompatibleWith(final CallInfo callInfo) {
-        Log.d(this, "isCompatibleWith %s", callInfo);
-        onFindSubscriptions(
-                callInfo.getHandle(),
-                new Response<Uri, Subscription>() {
-                    @Override
-                    public void onResult(Uri handle, Subscription... result) {
-                        boolean isCompatible = result.length > 0;
-                        Log.d(this, "adapter setIsCompatibleWith ");
-                        getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
-                    }
-
-                    @Override
-                    public void onError(Uri handle, int code, String msg) {
-                        Log.w(this, "Error in onFindSubscriptions %s %d %s", handle, code, msg);
-                        getAdapter().setIsCompatibleWith(callInfo.getId(), false);
-                    }
-                }
-        );
-    }
-
-    @Override
     public final void call(final CallInfo callInfo) {
         Log.d(this, "call %s", callInfo);
         onCreateConnections(
@@ -149,30 +171,24 @@
                         callInfo.getId(),
                         callInfo.getHandle(),
                         callInfo.getExtras()),
-                new Response<ConnectionRequest, Connection>() {
+                new OutgoingCallResponse<Connection>() {
                     @Override
-                    public void onResult(ConnectionRequest request, Connection... result) {
-                        if (result != null && result.length != 1) {
-                            Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
-                            getAdapter().handleFailedOutgoingCall(
-                                    request,
-                                    DisconnectCause.ERROR_UNSPECIFIED,
-                                    "Created " + result.length + " Connections, expected 1");
-                            for (Connection c : result) {
-                                c.abort();
-                            }
-                        } else {
-                            Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
-                                    callInfo.getId());
-                            getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
-                            addConnection(callInfo.getId(), result[0]);
-                        }
+                    public void onSuccess(ConnectionRequest request, Connection connection) {
+                        Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
+                                callInfo.getId());
+                        getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
+                        addConnection(callInfo.getId(), connection);
                     }
 
                     @Override
-                    public void onError(ConnectionRequest request, int code, String msg) {
+                    public void onFailure(ConnectionRequest request, int code, String msg) {
                         getAdapter().handleFailedOutgoingCall(request, code, msg);
                     }
+
+                    @Override
+                    public void onCancel(ConnectionRequest request) {
+                        getAdapter().cancelOutgoingCall(callInfo.getId());
+                    }
                 }
         );
     }
@@ -342,6 +358,71 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    protected void onAdapterAttached(CallServiceAdapter adapter) {
+        if (mAreSubscriptionsInitialized) {
+            // No need to query again if we already did it.
+            return;
+        }
+
+        getAdapter().queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
+            @Override
+            public void onResult(
+                    final List<ComponentName> componentNames,
+                    final List<IBinder> callServices) {
+                mHandler.post(new Runnable() {
+                    @Override public void run() {
+                        for (int i = 0; i < componentNames.size() && i < callServices.size(); i++) {
+                            mRemoteConnectionManager.addConnectionService(
+                                    componentNames.get(i),
+                                    ICallService.Stub.asInterface(callServices.get(i)));
+                        }
+                        mAreSubscriptionsInitialized = true;
+                        Log.d(this, "remote call services found: " + callServices);
+                        maybeRespondToSubscriptionLookup();
+                    }
+                });
+            }
+
+            @Override
+            public void onError() {
+                mHandler.post(new Runnable() {
+                    @Override public void run() {
+                        mAreSubscriptionsInitialized = true;
+                        maybeRespondToSubscriptionLookup();
+                    }
+                });
+            }
+        });
+    }
+
+    public void lookupRemoteSubscriptions(
+            Uri handle, SimpleResponse<Uri, List<Subscription>> response) {
+        mSubscriptionLookupResponse = response;
+        mSubscriptionLookupHandle = handle;
+        maybeRespondToSubscriptionLookup();
+    }
+
+    public void maybeRespondToSubscriptionLookup() {
+        if (mAreSubscriptionsInitialized && mSubscriptionLookupResponse != null) {
+            mSubscriptionLookupResponse.onResult(
+                    mSubscriptionLookupHandle,
+                    mRemoteConnectionManager.getSubscriptions(mSubscriptionLookupHandle));
+
+            mSubscriptionLookupHandle = null;
+            mSubscriptionLookupResponse = null;
+        }
+    }
+
+    public void createRemoteOutgoingConnection(
+            ConnectionRequest request,
+            OutgoingCallResponse<RemoteConnection> response) {
+        mRemoteConnectionManager.createOutgoingConnection(request, response);
+    }
+
+    /**
      * Returns all connections currently associated with this connection service.
      */
     public Collection<Connection> getAllConnections() {
@@ -349,16 +430,6 @@
     }
 
     /**
-     * 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.
-     * @param callback A callback for providing the result.
-     */
-    public void onFindSubscriptions(
-            Uri handle,
-            Response<Uri, Subscription> callback) {}
-
-    /**
      * Create a Connection given a request.
      *
      * @param request Data encapsulating details of the desired Connection.
@@ -366,7 +437,7 @@
      */
     public void onCreateConnections(
             ConnectionRequest request,
-            Response<ConnectionRequest, Connection> callback) {}
+            OutgoingCallResponse<Connection> callback) {}
 
     /**
      * Returns a new or existing conference connection when the the user elects to convert the
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 66974f9..8cbe2e6 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -38,6 +38,7 @@
     private final long mConnectTimeMillis;
     private final Uri mHandle;
     private final GatewayInfo mGatewayInfo;
+    private final Subscription mSubscription;
     private final CallServiceDescriptor mCurrentCallServiceDescriptor;
     private final CallServiceDescriptor mHandoffCallServiceDescriptor;
     private final String mParentCallId;
@@ -55,11 +56,12 @@
             long connectTimeMillis,
             Uri handle,
             GatewayInfo gatewayInfo,
+            Subscription subscription,
             CallServiceDescriptor descriptor,
             CallServiceDescriptor handoffDescriptor) {
         this(id, state, disconnectCauseCode, disconnectCauseMsg, cannedSmsResponses,
-                capabilities, connectTimeMillis, handle, gatewayInfo, descriptor, handoffDescriptor,
-                null, Collections.EMPTY_LIST);
+                capabilities, connectTimeMillis, handle, gatewayInfo, subscription, descriptor,
+                handoffDescriptor, null, Collections.EMPTY_LIST);
     }
 
     /** @hide */
@@ -73,6 +75,7 @@
             long connectTimeMillis,
             Uri handle,
             GatewayInfo gatewayInfo,
+            Subscription subscription,
             CallServiceDescriptor descriptor,
             CallServiceDescriptor handoffDescriptor,
             String parentCallId,
@@ -86,6 +89,7 @@
         mConnectTimeMillis = connectTimeMillis;
         mHandle = handle;
         mGatewayInfo = gatewayInfo;
+        mSubscription = subscription;
         mCurrentCallServiceDescriptor = descriptor;
         mHandoffCallServiceDescriptor = handoffDescriptor;
         mParentCallId = parentCallId;
@@ -145,6 +149,11 @@
         return mGatewayInfo;
     }
 
+    /** Subscription information for the call. */
+    public Subscription getSubscription() {
+        return mSubscription;
+    }
+
     /** The descriptor for the call service currently routing this call. */
     public CallServiceDescriptor getCurrentCallServiceDescriptor() {
         return mCurrentCallServiceDescriptor;
@@ -191,6 +200,7 @@
             long connectTimeMillis = source.readLong();
             Uri handle = source.readParcelable(classLoader);
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
+            Subscription subscription = source.readParcelable(classLoader);
             CallServiceDescriptor descriptor = source.readParcelable(classLoader);
             CallServiceDescriptor handoffDescriptor = source.readParcelable(classLoader);
             String parentCallId = source.readString();
@@ -198,7 +208,7 @@
             source.readList(childCallIds, classLoader);
             return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
                     cannedSmsResponses, capabilities, connectTimeMillis, handle, gatewayInfo,
-                    descriptor, handoffDescriptor, parentCallId, childCallIds);
+                    subscription, descriptor, handoffDescriptor, parentCallId, childCallIds);
         }
 
         @Override
@@ -225,6 +235,7 @@
         destination.writeLong(mConnectTimeMillis);
         destination.writeParcelable(mHandle, 0);
         destination.writeParcelable(mGatewayInfo, 0);
+        destination.writeParcelable(mSubscription, 0);
         destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
         destination.writeParcelable(mHandoffCallServiceDescriptor, 0);
         destination.writeString(mParentCallId);
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 3a63077..31291fb 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -156,7 +156,7 @@
     }
 
     /**
-     * @return The attached {@link CallServiceSelectorAdapter} if attached, or null otherwise.
+     * @return The attached {@link InCallAdapter} if attached, or null otherwise.
      */
     protected final InCallAdapter getAdapter() {
         return mAdapter;
@@ -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/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
new file mode 100644
index 0000000..92db0d8
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -0,0 +1,227 @@
+/*
+ * 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.telecomm;
+
+import android.net.Uri;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * RemoteConnection object used by RemoteConnectionService.
+ */
+public final class RemoteConnection {
+    public interface Listener {
+        void onStateChanged(RemoteConnection connection, int state);
+        void onAudioStateChanged(RemoteConnection connection, CallAudioState state);
+        void onDisconnected(RemoteConnection connection, int cause, String message);
+        void onRequestingRingback(RemoteConnection connection, boolean ringback);
+        void onPostDialWait(RemoteConnection connection, String remainingDigits);
+        void onDestroyed(RemoteConnection connection);
+    }
+
+    private final ICallService mCallService;
+    private final String mConnectionId;
+    private final Set<Listener> mListeners = new HashSet<>();
+
+    private int mState;
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private String mDisconnectMessage;
+    private boolean mRequestingRingback;
+    private boolean mConnected;
+
+    /**
+     * @hide
+     */
+    RemoteConnection(ICallService callService, String connectionId) {
+        mCallService = callService;
+        mConnectionId = connectionId;
+
+        mConnected = true;
+    }
+
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    public int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public String getDisconnectMessage() {
+        return mDisconnectMessage;
+    }
+
+    public void answer() {
+        try {
+            if (mConnected) {
+                mCallService.answer(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void reject() {
+        try {
+            if (mConnected) {
+                mCallService.reject(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void hold() {
+        try {
+            if (mConnected) {
+                mCallService.hold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void unhold() {
+        try {
+            if (mConnected) {
+                mCallService.unhold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void disconnect() {
+        try {
+            if (mConnected) {
+                mCallService.disconnect(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void playDtmf(char digit) {
+        try {
+            if (mConnected) {
+                mCallService.playDtmfTone(mConnectionId, digit);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void stopDtmf() {
+        try {
+            if (mConnected) {
+                mCallService.stopDtmfTone(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void postDialContinue(boolean proceed) {
+        try {
+            if (mConnected) {
+                mCallService.onPostDialContinue(mConnectionId, proceed);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setState(int state) {
+        if (mState != state) {
+            mState = state;
+            for (Listener l: mListeners) {
+                l.onStateChanged(this, state);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setAudioState(CallAudioState state) {
+        for (Listener l: mListeners) {
+            l.onAudioStateChanged(this, state);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDisconnected(int cause, String message) {
+        if (mState != Connection.State.DISCONNECTED) {
+            mState = Connection.State.DISCONNECTED;
+            mDisconnectCause = cause;
+            mDisconnectMessage = message;
+
+            for (Listener l : mListeners) {
+                l.onDisconnected(this, cause, message);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setRequestingRingback(boolean ringback) {
+        if (mRequestingRingback != ringback) {
+            mRequestingRingback = ringback;
+            for (Listener l : mListeners) {
+                l.onRequestingRingback(this, ringback);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDestroyed() {
+        if (!mListeners.isEmpty()) {
+            // Make sure that the listeners are notified that the call is destroyed first.
+            if (mState != Connection.State.DISCONNECTED) {
+                setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed.");
+            }
+
+            Set<Listener> listeners = new HashSet<Listener>(mListeners);
+            mListeners.clear();
+            for (Listener l : listeners) {
+                l.onDestroyed(this);
+            }
+
+            mConnected = false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setPostDialWait(String remainingDigits) {
+        for (Listener l : mListeners) {
+            l.onPostDialWait(this, remainingDigits);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
new file mode 100644
index 0000000..465fae0
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class RemoteConnectionManager {
+    private Map<ComponentName, RemoteConnectionService> mRemoteConnectionServices = new HashMap<>();
+
+    void addConnectionService(ComponentName componentName, ICallService callService) {
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            try {
+                RemoteConnectionService remoteConnectionService =
+                        new RemoteConnectionService(componentName, callService);
+                mRemoteConnectionServices.put(componentName, remoteConnectionService);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    List<Subscription> getSubscriptions(Uri handle) {
+        List<Subscription> subscriptions = new LinkedList<>();
+        Log.d(this, "Getting subscriptions: " + mRemoteConnectionServices.keySet());
+        for (RemoteConnectionService remoteService : mRemoteConnectionServices.values()) {
+            // TODO(santoscordon): Eventually this will be async.
+            subscriptions.addAll(remoteService.lookupSubscriptions(handle));
+        }
+        return subscriptions;
+    }
+
+    public void createOutgoingConnection(
+            ConnectionRequest request,
+            final ConnectionService.OutgoingCallResponse response) {
+        Subscription subscription = request.getSubscription();
+        if (subscription == null) {
+            throw new IllegalArgumentException("subscription must be specified.");
+        }
+
+        ComponentName componentName = request.getSubscription().getComponentName();
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            throw new UnsupportedOperationException("subscription not supported: " + componentName);
+        } else {
+            RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+            remoteService.createOutgoingConnection(request, response);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
new file mode 100644
index 0000000..64e5871
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -0,0 +1,291 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import android.text.TextUtils;
+
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.ICallVideoProvider;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Remote connection service which other connection services can use to place calls on their behalf.
+ */
+public class RemoteConnectionService implements DeathRecipient {
+    private final ICallService mCallService;
+    private final ComponentName mComponentName;
+
+    private String mConnectionId;
+    private ConnectionRequest mPendingRequest;
+    private ConnectionService.OutgoingCallResponse<RemoteConnection> mPendingOutgoingCallResponse;
+    // Remote connection services only support a single connection.
+    private RemoteConnection mConnection;
+
+    private final ICallServiceAdapter mAdapter = new ICallServiceAdapter.Stub() {
+
+        /** ${inheritDoc} */
+            @Override
+        public void notifyIncomingCall(CallInfo callInfo) {
+            Log.w(this, "notifyIncomingCall not implemented in Remote connection");
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handleSuccessfulOutgoingCall(String connectionId) {
+            if (isPendingConnection(connectionId)) {
+                mConnection = new RemoteConnection(mCallService, connectionId);
+                mPendingOutgoingCallResponse.onSuccess(mPendingRequest, mConnection);
+                clearPendingInformation();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handleFailedOutgoingCall(
+                ConnectionRequest request, int errorCode, String errorMessage) {
+            if (isPendingConnection(request.getCallId())) {
+                // Use mPendingRequest instead of request so that we use the same object that was
+                // passed in to us.
+                mPendingOutgoingCallResponse.onFailure(mPendingRequest, errorCode, errorMessage);
+                mConnectionId = null;
+                clearPendingInformation();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void cancelOutgoingCall(String connectionId) {
+            if (isPendingConnection(connectionId)) {
+                mPendingOutgoingCallResponse.onCancel(mPendingRequest);
+                mConnectionId = null;
+                clearPendingInformation();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setActive(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.ACTIVE);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setRinging(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.RINGING);
+            }
+        }
+
+        /** ${inheritDoc} */
+        public void setCallVideoProvider(
+                String connectionId, ICallVideoProvider callVideoProvider) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setDialing(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.DIALING);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setDisconnected(
+                String connectionId, int disconnectCause, String disconnectMessage) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setDisconnected(disconnectCause, disconnectMessage);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setOnHold(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.HOLDING);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setRequestingRingback(isRequestingRingback);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setCanConference(String connectionId, boolean canConference) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setIsConferenced(String connectionId, String conferenceConnectionId) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void addConferenceCall(String connectionId, CallInfo callInfo) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void removeCall(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                destroyConnection();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void onPostDialWait(String connectionId, String remainingDigits) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setPostDialWait(remainingDigits);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handoffCall(String connectionId) {
+            // unnecessary.
+        }
+
+        /** ${inheritDoc} */
+        @Override
+        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+            try {
+                // Not supported from remote connection service.
+                callback.onError();
+            } catch (RemoteException e) {
+            }
+        }
+    };
+
+    RemoteConnectionService(ComponentName componentName, ICallService callService)
+            throws RemoteException {
+        mComponentName = componentName;
+        mCallService = callService;
+
+        // TODO(santoscordon): Rename from setCallServiceAdapter to addCallServiceAdapter.
+        mCallService.setCallServiceAdapter(mAdapter);
+        mCallService.asBinder().linkToDeath(this, 0);
+    }
+
+    @Override
+    public String toString() {
+        return "[RemoteCS - " + mCallService.asBinder().toString() + "]";
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void binderDied() {
+        if (mConnection != null) {
+            destroyConnection();
+        }
+
+        release();
+    }
+
+    /**
+     * Places an outgoing call.
+     */
+    public void createOutgoingConnection(
+            ConnectionRequest request,
+            ConnectionService.OutgoingCallResponse<RemoteConnection> response) {
+
+        if (mConnectionId == null) {
+            String id = UUID.randomUUID().toString();
+            CallInfo callInfo = new CallInfo(id, CallState.NEW, request.getHandle());
+            try {
+                mCallService.call(callInfo);
+                mConnectionId = id;
+                mPendingOutgoingCallResponse = response;
+                mPendingRequest = request;
+            } catch (RemoteException e) {
+                response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
+            }
+        } else {
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+        }
+    }
+
+    // TODO(santoscordon): Handle incoming connections
+    // public void handleIncomingConnection() {}
+
+    public List<Subscription> lookupSubscriptions(Uri handle) {
+        // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
+        // each time.
+        List<Subscription> subscriptions = new LinkedList<>();
+        subscriptions.add(new Subscription(
+                mComponentName,
+                null /* id */,
+                null /* handle */,
+                0 /* labelResId */,
+                0 /* shortDescriptionResId */,
+                0 /* iconResId */,
+                true /* isEnabled */,
+                false /* isSystemDefault */));
+        return subscriptions;
+    }
+
+    /**
+     * Releases the resources associated with this Remote connection service. Should be called when
+     * the remote service is no longer being used.
+     */
+    void release() {
+        mCallService.asBinder().unlinkToDeath(this, 0);
+    }
+
+    private boolean isPendingConnection(String id) {
+        return TextUtils.equals(mConnectionId, id) && mPendingOutgoingCallResponse != null;
+    }
+
+    private boolean isCurrentConnection(String id) {
+        return mConnection != null && TextUtils.equals(mConnectionId, id);
+    }
+
+    private void clearPendingInformation() {
+        mPendingRequest = null;
+        mPendingOutgoingCallResponse = null;
+    }
+
+    private void destroyConnection() {
+        mConnection.setDestroyed();
+        mConnection = null;
+        mConnectionId = null;
+    }
+}
diff --git a/telecomm/java/android/telecomm/SimpleResponse.java b/telecomm/java/android/telecomm/SimpleResponse.java
new file mode 100644
index 0000000..8e84adb
--- /dev/null
+++ b/telecomm/java/android/telecomm/SimpleResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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.telecomm;
+
+/**
+ * Used to inform a client of asynchronously returned results.
+ */
+public interface SimpleResponse<IN, OUT> {
+
+    /**
+     * Provide a set of results.
+     *
+     * @param request The original request.
+     * @param result The results.
+     */
+    void onResult(IN request, OUT result);
+
+    /**
+     * Indicates the inability to provide results.
+     *
+     * @param request The original request.
+     */
+    void onError(IN request);
+}
diff --git a/telecomm/java/android/telecomm/Subscription.aidl b/telecomm/java/android/telecomm/Subscription.aidl
new file mode 100644
index 0000000..6327fcc
--- /dev/null
+++ b/telecomm/java/android/telecomm/Subscription.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2008 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.telecomm;
+
+/**
+ * {@hide}
+  */
+parcelable Subscription;
diff --git a/telecomm/java/android/telecomm/Subscription.java b/telecomm/java/android/telecomm/Subscription.java
index f187f4d..964db4a 100644
--- a/telecomm/java/android/telecomm/Subscription.java
+++ b/telecomm/java/android/telecomm/Subscription.java
@@ -16,25 +16,169 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Rlog;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.util.MissingResourceException;
 
 /**
  * Represents a distinct subscription, line of service or call placement method that
- * a {@link ConnectionService} can use to place phone calls.
+ * the system can use to place phone calls.
  */
 public class Subscription implements Parcelable {
 
-    public Subscription() {}
+    private static final int NO_DENSITY = -1;
+
+    private static final String LOG_TAG = "Subscription";
+
+    private final ComponentName mComponentName;
+    private final String mId;
+    private final Uri mHandle;
+    private final int mLabelResId;
+    private final int mShortDescriptionResId;
+    private final int mIconResId;
+    private final boolean mIsEnabled;
+    private final boolean mIsSystemDefault;
+
+    public Subscription(
+            ComponentName componentName,
+            String id,
+            Uri handle,
+            int labelResId,
+            int shortDescriptionResId,
+            int iconResId,
+            boolean isEnabled,
+            boolean isSystemDefault) {
+        mComponentName = componentName;
+        mId = id;
+        mHandle = handle;
+        mLabelResId = labelResId;
+        mShortDescriptionResId = shortDescriptionResId;
+        mIconResId = iconResId;
+        mIsSystemDefault = isSystemDefault;
+        mIsEnabled = isEnabled;
+    }
+
+    /**
+     * The {@code ComponentName} of the {@link android.telecomm.ConnectionService} which is
+     * responsible for making phone calls using this {@code Subscription}.
+     *
+     * @return A suitable {@code ComponentName}.
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * A unique identifier for this {@code Subscription}, generated by and meaningful to the
+     * {@link android.telecomm.ConnectionService} that created it.
+     *
+     * @return A unique identifier for this {@code Subscription}.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * The handle (e.g., a phone number) associated with this {@code Subscription}. This represents
+     * the destination from which outgoing calls using this {@code Subscription} will appear to come
+     * from, if applicable, and the destination to which incoming calls using this
+     * {@code Subscription} may be addressed.
+     *
+     * @return A handle expressed as a {@code Uri}, for example, a phone number.
+     */
+    public Uri getHandle() {
+        return mHandle;
+    }
+
+    /**
+     * A short string label describing this {@code Subscription}.
+     *
+     * @param context The invoking {@code Context}, used for retrieving resources.
+     *
+     * @return A label for this {@code Subscription}.
+     */
+    public String getLabel(Context context) {
+        return getString(context, mLabelResId);
+    }
+
+    /**
+     * A short paragraph describing this {@code Subscription}.
+     *
+     * @param context The invoking {@code Context}, used for retrieving resources.
+     *
+     * @return A description for this {@code Subscription}.
+     */
+    public String getShortDescription(Context context) {
+        return getString(context, mShortDescriptionResId);
+    }
+
+    /**
+     * An icon to represent this {@code Subscription} in a user interface.
+     *
+     * @param context The invoking {@code Context}, used for retrieving resources.
+     *
+     * @return An icon for this {@code Subscription}.
+     */
+    public Drawable getIcon(Context context) {
+        return getIcon(context, mIconResId, NO_DENSITY);
+    }
+
+    /**
+     * An icon to represent this {@code Subscription} in a user interface.
+     *
+     * @param context The invoking {@code Context}, used for retrieving resources.
+     * @param density A display density from {@link DisplayMetrics}.
+     *
+     * @return An icon for this {@code Subscription}.
+     */
+    public Drawable getIcon(Context context, int density) {
+        return getIcon(context, mIconResId, density);
+    }
+
+    /**
+     * Whether this {@code Subscription} is enabled for use.
+     *
+     * @return {@code true} if this {@code Subscription} is enabled.
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * Whether this {@code Subscription} is the system default.
+     *
+     * @return {@code true} if this {@code Subscription} is the system default.
+     */
+    public boolean isSystemDefault() {
+        return mIsSystemDefault;
+    }
 
     public int describeContents() {
         return 0;
     }
 
-    public void writeToParcel(Parcel out, int flags) {}
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mComponentName, flags);
+        out.writeString(mId);
+        out.writeString(mHandle != null ? mHandle.toString() : "");
+        out.writeInt(mLabelResId);
+        out.writeInt(mShortDescriptionResId);
+        out.writeInt(mIconResId);
+        out.writeInt(mIsEnabled ? 1 : 0);
+        out.writeInt(mIsSystemDefault ? 1 : 0);
+    }
 
-    public static final Parcelable.Creator<Subscription> CREATOR
-            = new Parcelable.Creator<Subscription>() {
+    public static final Creator<Subscription> CREATOR
+            = new Creator<Subscription>() {
         public Subscription createFromParcel(Parcel in) {
             return new Subscription(in);
         }
@@ -44,5 +188,54 @@
         }
     };
 
-    private Subscription(Parcel in) {}
+    private Subscription(Parcel in) {
+        mComponentName = in.readParcelable(getClass().getClassLoader());
+        mId = in.readString();
+        String uriString = in.readString();
+        mHandle = uriString.length() > 0 ? Uri.parse(uriString) : null;
+        mLabelResId = in.readInt();
+        mShortDescriptionResId = in.readInt();
+        mIconResId = in.readInt();
+        mIsEnabled = in.readInt() == 1;
+        mIsSystemDefault = in.readInt() == 1;
+    }
+
+    private String getString(Context context, int resId) {
+        Context packageContext;
+        try {
+            packageContext = context.createPackageContext(mComponentName.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (Rlog.isLoggable(LOG_TAG, Log.WARN)) {
+                Rlog.w(LOG_TAG, "Cannot find package " + mComponentName.getPackageName());
+            }
+            return null;
+        }
+        String result = packageContext.getString(resId);
+        if (result == null && Rlog.isLoggable(LOG_TAG, Log.WARN)) {
+            Rlog.w(LOG_TAG, "Cannot find string " + resId + " in package " +
+                    mComponentName.getPackageName());
+        }
+        return result;
+    }
+
+    private Drawable getIcon(Context context, int resId, int density) {
+        Context packageContext;
+        try {
+            packageContext = context.createPackageContext(mComponentName.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (Rlog.isLoggable(LOG_TAG, Log.WARN)) {
+                Rlog.w(LOG_TAG, "Cannot find package " + mComponentName.getPackageName());
+            }
+            return null;
+        }
+        try {
+            return density == NO_DENSITY ?
+                    packageContext.getResources().getDrawable(resId) :
+                    packageContext.getResources().getDrawableForDensity(resId, density);
+        } catch (MissingResourceException e) {
+            Rlog.e(LOG_TAG, "Cannot find icon " + resId + " in package " +
+                    mComponentName.getPackageName() + ": " + e.toString());
+            return null;
+        }
+    }
 }
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index 8300c92..4c08da0 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -16,6 +16,7 @@
 
 package android.telecomm;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.telephony.TelephonyManager;
 
@@ -50,9 +51,11 @@
     public static final String ACTION_CALL_SERVICE = CallService.class.getName();
 
     /**
-     * The service action used to bind to {@link CallServiceSelector} implementations.
+     * Optional extra for {@link Intent#ACTION_CALL} containing a boolean that determines whether
+     * the speakerphone should be automatically turned on for an outgoing call.
      */
-    public static final String ACTION_CALL_SERVICE_SELECTOR = CallServiceSelector.class.getName();
+    public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE =
+            "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
 
     /**
      * Extra for {@link #ACTION_INCOMING_CALL} containing the {@link CallServiceDescriptor} that
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
new file mode 100644
index 0000000..a0abc28
--- /dev/null
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -0,0 +1,75 @@
+/*
+ * 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.telecomm;
+
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.telecomm.ITelecommService;
+
+/**
+ * Provides access to Telecomm-related functionality.
+ */
+public class TelecommManager {
+    private static final String TAG = "TelecommManager";
+    private static final String TELECOMM_SERVICE_NAME = "telecomm";
+
+    private final Context mContext;
+    private final ITelecommService mService;
+
+    /**
+     * @hide
+     */
+    public TelecommManager(Context context, ITelecommService service) {
+        Context appContext = context.getApplicationContext();
+        if (appContext != null) {
+            mContext = appContext;
+        } else {
+            mContext = context;
+        }
+
+        mService = service;
+    }
+
+    /**
+     * @hide
+     */
+    public static TelecommManager from(Context context) {
+        return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
+    }
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    public ComponentName getDefaultPhoneApp() {
+        try {
+            return getTelecommService().getDefaultPhoneApp();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get the default phone app.", e);
+        }
+        return null;
+    }
+
+    private ITelecommService getTelecommService() {
+        return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
+    }
+}
diff --git a/telecomm/java/android/telecomm/VideoCallProfile.aidl b/telecomm/java/android/telecomm/VideoCallProfile.aidl
new file mode 100644
index 0000000..adbf94d
--- /dev/null
+++ b/telecomm/java/android/telecomm/VideoCallProfile.aidl
@@ -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 android.telecomm;
+
+parcelable VideoCallProfile;
diff --git a/telecomm/java/android/telecomm/VideoCallProfile.java b/telecomm/java/android/telecomm/VideoCallProfile.java
new file mode 100644
index 0000000..fc7b2c3
--- /dev/null
+++ b/telecomm/java/android/telecomm/VideoCallProfile.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 android.telecomm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents attributes of video calls.
+ */
+public class VideoCallProfile implements Parcelable {
+    /**
+     * Call is currently in an audio-only mode with no video transmission or receipt.
+     */
+    public static final int VIDEO_STATE_AUDIO_ONLY = 0x0;
+
+    /**
+     * Video transmission is enabled.
+     */
+    public static final int VIDEO_STATE_TX_ENABLED = 0x1;
+
+    /**
+     * Video reception is enabled.
+     */
+    public static final int VIDEO_STATE_RX_ENABLED = 0x2;
+
+    /**
+     * Video signal is bi-directional.
+     */
+    public static final int VIDEO_STATE_BIDIRECTIONAL =
+            VIDEO_STATE_TX_ENABLED | VIDEO_STATE_RX_ENABLED;
+
+    /**
+     * Video is paused.
+     */
+    public static final int VIDEO_STATE_PAUSED = 0x4;
+
+    /**
+     * "High" video quality.
+     */
+    public static final int QUALITY_HIGH = 1;
+
+    /**
+     * "Medium" video quality.
+     */
+    public static final int QUALITY_MEDIUM = 2;
+
+    /**
+     * "Low" video quality.
+     */
+    public static final int QUALITY_LOW = 3;
+
+    /**
+     * Use default video quality.
+     */
+    public static final int QUALITY_DEFAULT = 4;
+
+    private final int mVideoState;
+
+    private final int mQuality;
+
+    /**
+     * Creates an instance of the VideoCallProfile
+     *
+     * @param videoState The video state.
+     * @param quality The video quality.
+     */
+    public VideoCallProfile(int videoState, int quality) {
+        mVideoState = videoState;
+        mQuality = quality;
+    }
+
+    /**
+     * The video state of the call.  Stored as a bit-field describing whether video transmission and
+     * receipt it enabled, as well as whether the video is currently muted.
+     * Valid values: {@link VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
+     * {@link VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
+     * {@link VideoCallProfile#VIDEO_STATE_TX_ENABLED},
+     * {@link VideoCallProfile#VIDEO_STATE_RX_ENABLED},
+     * {@link VideoCallProfile#VIDEO_STATE_PAUSED}.
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    /**
+     * The desired video quality for the call.
+     * Valid values: {@link VideoCallProfile#QUALITY_HIGH}, {@link VideoCallProfile#QUALITY_MEDIUM},
+     * {@link VideoCallProfile#QUALITY_LOW}, {@link VideoCallProfile#QUALITY_DEFAULT}.
+     */
+    public int getQuality() {
+        return mQuality;
+    }
+
+    /**
+     * Responsible for creating VideoCallProfile objects from deserialized Parcels.
+     **/
+    public static final Parcelable.Creator<VideoCallProfile> CREATOR =
+            new Parcelable.Creator<VideoCallProfile> () {
+                /**
+                 * Creates a MediaProfile instances from a parcel.
+                 *
+                 * @param source The parcel.
+                 * @return The MediaProfile.
+                 */
+                @Override
+                public VideoCallProfile createFromParcel(Parcel source) {
+                    int state = source.readInt();
+                    int quality = source.readInt();
+
+                    ClassLoader classLoader = VideoCallProfile.class.getClassLoader();
+                    return new VideoCallProfile(state, quality);
+                }
+
+                @Override
+                public VideoCallProfile[] newArray(int size) {
+                    return new VideoCallProfile[size];
+                }
+            };
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     *
+     * @return a bitmask indicating the set of special object types marshalled
+     * by the Parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mVideoState);
+        dest.writeInt(mQuality);
+    }
+}
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..62ebd54 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
@@ -32,8 +32,6 @@
 oneway interface ICallService {
     void setCallServiceAdapter(in ICallServiceAdapter callServiceAdapter);
 
-    void isCompatibleWith(in CallInfo callInfo);
-
     void call(in CallInfo callInfo);
 
     void abort(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index ab269bb..b81ef37 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -19,6 +19,9 @@
 import android.telecomm.CallInfo;
 import android.telecomm.ConnectionRequest;
 
+import com.android.internal.telecomm.ICallVideoProvider;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
 /**
  * Internal remote callback interface for call services.
  *
@@ -27,14 +30,14 @@
  * {@hide}
  */
 oneway interface ICallServiceAdapter {
-    void setIsCompatibleWith(String callId, boolean isCompatible);
-
     void notifyIncomingCall(in CallInfo callInfo);
 
     void handleSuccessfulOutgoingCall(String callId);
 
     void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
 
+    void cancelOutgoingCall(String callId);
+
     void setActive(String callId);
 
     void setRinging(String callId);
@@ -56,4 +59,10 @@
     void removeCall(String callId);
 
     void onPostDialWait(String callId, String remaining);
+
+    void handoffCall(String callId);
+
+    void queryRemoteConnectionServices(RemoteServiceCallback callback);
+
+    void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceSelector.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceSelector.aidl
deleted file mode 100644
index 9597dc1..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceSelector.aidl
+++ /dev/null
@@ -1,42 +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.internal.telecomm;
-
-import android.telecomm.CallInfo;
-import android.telecomm.CallServiceDescriptor;
-
-import com.android.internal.telecomm.ICallService;
-import com.android.internal.telecomm.ICallServiceSelectorAdapter;
-
-import java.util.List;
-
-/**
- * Internal remote interface for call service selectors.
- *
- * @see android.telecomm.CallServiceSelector
- *
- * @hide
- */
-oneway interface ICallServiceSelector {
-    void setCallServiceSelectorAdapter(in ICallServiceSelectorAdapter adapter);
-
-    void select(in CallInfo callInfo, in List<CallServiceDescriptor> callServiceDescriptors);
-
-    void onCallUpdated(in CallInfo callInfo);
-
-    void onCallRemoved(String callId);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl
deleted file mode 100644
index ad71e3c..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceSelectorAdapter.aidl
+++ /dev/null
@@ -1,41 +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.internal.telecomm;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.telecomm.CallInfo;
-import android.telecomm.CallServiceDescriptor;
-
-import java.util.List;
-
-/**
- * Internal remote interface for call service selector adapter.
- *
- * @see android.telecomm.CallServiceSelectorAdapter
- *
- * @hide
- */
-oneway interface ICallServiceSelectorAdapter {
-    void setSelectedCallServices(
-            String callId,
-            in List<CallServiceDescriptor> selectedCallServiceDescriptors);
-
-    void cancelOutgoingCall(String callId);
-
-    void setHandoffInfo(String callId, in Uri handle, in Bundle extras);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl b/telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl
new file mode 100644
index 0000000..0a854a1
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl
@@ -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.internal.telecomm;
+
+import android.telecomm.CallCameraCapabilities;
+import android.telecomm.VideoCallProfile;
+
+ /**
+  * Internal definition of the CallVideoClient, used for an InCall-UI to respond to video telephony
+  * related changes.
+  *
+  * @see android.telecomm.CallVideoClient
+  *
+  * {@hide}
+  */
+ oneway interface ICallVideoClient {
+
+     void onReceiveSessionModifyRequest(in VideoCallProfile videoCallProfile);
+
+     void onReceiveSessionModifyResponse(int status, in VideoCallProfile requestedProfile,
+            in VideoCallProfile responseProfile);
+
+     void onCallSessionEvent(int event);
+
+     void onUpdatedPeerDimensions(int width, int height);
+
+     void onUpdateCallDataUsage(int dataUsage);
+
+     void onCameraCapabilitiesChange(in CallCameraCapabilities callCameraCapabilities);
+ }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl b/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl
new file mode 100644
index 0000000..c116dcb
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.telecomm;
+
+/**
+ * Internal remote interface for a call video provider.
+ * @see android.telecomm.CallVideoProvider
+ * @hide
+ */
+oneway interface ICallVideoProvider {
+    void setCamera(String cameraId);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 0e94ffb..2ae5768 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -16,6 +16,9 @@
 
 package com.android.internal.telecomm;
 
+import android.content.ComponentName;
+import android.telecomm.Subscription;
+
 /**
  * Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing
  * commands that were previously handled by ITelephony.
@@ -38,4 +41,24 @@
      * @param showDialpad if true, make the dialpad visible initially.
      */
     void showCallScreen(boolean showDialpad);
+
+    /**
+     * Gets a list of Subscriptions.
+     */
+    List<Subscription> getSubscriptions();
+
+    /**
+     * Sets the enabled state of a given Subscription.
+     */
+    void setEnabled(in Subscription subscription, boolean enabled);
+
+    /**
+     * Sets a given Subscription as the system default.
+     */
+    void setSystemDefault(in Subscription subscription);
+
+    /**
+     * Returns the component name of the default phone application.
+     */
+    ComponentName getDefaultPhoneApp();
 }
diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
new file mode 100644
index 0000000..42c77d7
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.telecomm;
+
+import android.content.ComponentName;
+
+/**
+ * Simple response callback object.
+ */
+oneway interface RemoteServiceCallback {
+    void onError();
+    void onResult(in List<ComponentName> components, in List<IBinder> callServices);
+}
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..b935d2a 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -36,6 +36,7 @@
 import android.telephony.Rlog;
 import android.util.SparseIntArray;
 
+import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
@@ -166,7 +167,9 @@
         // TODO: We don't check for SecurityException here (requires
         // CALL_PRIVILEGED permission).
         if (scheme.equals("voicemail")) {
-            return TelephonyManager.getDefault().getCompleteVoiceMailNumber();
+            long subId = intent.getLongExtra(SUBSCRIPTION_KEY,
+                    SubscriptionManager.getDefaultVoiceSubId());
+            return TelephonyManager.getDefault().getCompleteVoiceMailNumber(subId);
         }
 
         if (context == null) {
@@ -1144,7 +1147,7 @@
      * @return A locally acceptable formatting of the input, or the raw input if
      *  formatting rules aren't known for the number
      *
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static String formatNumber(String source) {
         SpannableStringBuilder text = new SpannableStringBuilder(source);
@@ -1162,7 +1165,7 @@
      * @return The phone number formatted with the given formatting type.
      *
      * @hide
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static String formatNumber(String source, int defaultFormattingType) {
         SpannableStringBuilder text = new SpannableStringBuilder(source);
@@ -1177,7 +1180,7 @@
      * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
      * rules are not known for the given locale
      *
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static int getFormatTypeForLocale(Locale locale) {
         String country = locale.getCountry();
@@ -1193,7 +1196,7 @@
      * @param defaultFormattingType The default formatting rules to apply if the number does
      * not begin with +[country_code]
      *
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static void formatNumber(Editable text, int defaultFormattingType) {
         int formatType = defaultFormattingType;
@@ -1241,7 +1244,7 @@
      *
      * @param text the number to be formatted, will be modified with the formatting
      *
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static void formatNanpNumber(Editable text) {
         int length = text.length();
@@ -1356,7 +1359,7 @@
      * @param text the number to be formatted, will be modified with
      * the formatting
      *
-     * @deprecated Use {@link #formatNumber(String phoneNumber, String defaultCountryIso)} instead
+     * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
      */
     public static void formatJapaneseNumber(Editable text) {
         JapanesePhoneNumberFormatter.format(text);
@@ -1560,9 +1563,23 @@
      *         listed in the RIL / SIM, otherwise return false.
      */
     public static boolean isEmergencyNumber(String number) {
+        return isEmergencyNumber(getDefaultVoiceSubId(), number);
+    }
+
+    /**
+     * Checks a given number against the list of
+     * emergency numbers provided by the RIL and SIM card.
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @return true if the number is in the list of emergency numbers
+     *         listed in the RIL / SIM, otherwise return false.
+     * @hide
+     */
+    public static boolean isEmergencyNumber(long subId, String number) {
         // Return true only if the specified number *exactly* matches
         // one of the emergency numbers listed by the RIL / SIM.
-        return isEmergencyNumberInternal(number, true /* useExactMatch */);
+        return isEmergencyNumberInternal(subId, number, true /* useExactMatch */);
     }
 
     /**
@@ -1586,9 +1603,33 @@
      * @hide
      */
     public static boolean isPotentialEmergencyNumber(String number) {
+        return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number);
+    }
+
+    /**
+     * Checks if given number might *potentially* result in
+     * a call to an emergency service on the current network.
+     *
+     * Specifically, this method will return true if the specified number
+     * is an emergency number according to the list managed by the RIL or
+     * SIM, *or* if the specified number simply starts with the same
+     * digits as any of the emergency numbers listed in the RIL / SIM.
+     *
+     * This method is intended for internal use by the phone app when
+     * deciding whether to allow ACTION_CALL intents from 3rd party apps
+     * (where we're required to *not* allow emergency calls to be placed.)
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @return true if the number is in the list of emergency numbers
+     *         listed in the RIL / SIM, *or* if the number starts with the
+     *         same digits as any of those emergency numbers.
+     * @hide
+     */
+    public static boolean isPotentialEmergencyNumber(long subId, String number) {
         // Check against the emergency numbers listed by the RIL / SIM,
         // and *don't* require an exact match.
-        return isEmergencyNumberInternal(number, false /* useExactMatch */);
+        return isEmergencyNumberInternal(subId, number, false /* useExactMatch */);
     }
 
     /**
@@ -1611,7 +1652,32 @@
      *         listed in the RIL / sim, otherwise return false.
      */
     private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) {
-        return isEmergencyNumberInternal(number, null, useExactMatch);
+        return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch);
+    }
+
+    /**
+     * Helper function for isEmergencyNumber(String) and
+     * isPotentialEmergencyNumber(String).
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     *
+     * @param useExactMatch if true, consider a number to be an emergency
+     *           number only if it *exactly* matches a number listed in
+     *           the RIL / SIM.  If false, a number is considered to be an
+     *           emergency number if it simply starts with the same digits
+     *           as any of the emergency numbers listed in the RIL / SIM.
+     *           (Setting useExactMatch to false allows you to identify
+     *           number that could *potentially* result in emergency calls
+     *           since many networks will actually ignore trailing digits
+     *           after a valid emergency number.)
+     *
+     * @return true if the number is in the list of emergency numbers
+     *         listed in the RIL / sim, otherwise return false.
+     */
+    private static boolean isEmergencyNumberInternal(long subId, String number,
+            boolean useExactMatch) {
+        return isEmergencyNumberInternal(subId, number, null, useExactMatch);
     }
 
     /**
@@ -1625,7 +1691,21 @@
      * @hide
      */
     public static boolean isEmergencyNumber(String number, String defaultCountryIso) {
-        return isEmergencyNumberInternal(number,
+            return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
+    }
+
+    /**
+     * Checks if a given number is an emergency number for a specific country.
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param defaultCountryIso the specific country which the number should be checked against
+     * @return if the number is an emergency number for the specific country, then return true,
+     * otherwise false
+     * @hide
+     */
+    public static boolean isEmergencyNumber(long subId, String number, String defaultCountryIso) {
+        return isEmergencyNumberInternal(subId, number,
                                          defaultCountryIso,
                                          true /* useExactMatch */);
     }
@@ -1652,7 +1732,33 @@
      * @hide
      */
     public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) {
-        return isEmergencyNumberInternal(number,
+        return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
+    }
+
+    /**
+     * Checks if a given number might *potentially* result in a call to an
+     * emergency service, for a specific country.
+     *
+     * Specifically, this method will return true if the specified number
+     * is an emergency number in the specified country, *or* if the number
+     * simply starts with the same digits as any emergency number for that
+     * country.
+     *
+     * This method is intended for internal use by the phone app when
+     * deciding whether to allow ACTION_CALL intents from 3rd party apps
+     * (where we're required to *not* allow emergency calls to be placed.)
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param defaultCountryIso the specific country which the number should be checked against
+     * @return true if the number is an emergency number for the specific
+     *         country, *or* if the number starts with the same digits as
+     *         any of those emergency numbers.
+     * @hide
+     */
+    public static boolean isPotentialEmergencyNumber(long subId, String number,
+            String defaultCountryIso) {
+        return isEmergencyNumberInternal(subId, number,
                                          defaultCountryIso,
                                          false /* useExactMatch */);
     }
@@ -1674,6 +1780,29 @@
     private static boolean isEmergencyNumberInternal(String number,
                                                      String defaultCountryIso,
                                                      boolean useExactMatch) {
+        return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso,
+                useExactMatch);
+    }
+
+    /**
+     * Helper function for isEmergencyNumber(String, String) and
+     * isPotentialEmergencyNumber(String, String).
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param defaultCountryIso the specific country which the number should be checked against
+     * @param useExactMatch if true, consider a number to be an emergency
+     *           number only if it *exactly* matches a number listed in
+     *           the RIL / SIM.  If false, a number is considered to be an
+     *           emergency number if it simply starts with the same digits
+     *           as any of the emergency numbers listed in the RIL / SIM.
+     *
+     * @return true if the number is an emergency number for the specified country.
+     * @hide
+     */
+    private static boolean isEmergencyNumberInternal(long subId, String number,
+                                                     String defaultCountryIso,
+                                                     boolean useExactMatch) {
         // If the number passed in is null, just return false:
         if (number == null) return false;
 
@@ -1692,9 +1821,14 @@
         // to the list.
         number = extractNetworkPortionAlt(number);
 
+        String numbers = "";
+        int slotId = SubscriptionManager.getSlotId(subId);
         // retrieve the list of emergency numbers
         // check read-write ecclist property first
-        String numbers = SystemProperties.get("ril.ecclist");
+        String ecclist = (slotId == 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
+
+        numbers = SystemProperties.get(ecclist);
+
         if (TextUtils.isEmpty(numbers)) {
             // then read-only ecclist property since old RIL only uses this
             numbers = SystemProperties.get("ro.ril.ecclist");
@@ -1742,14 +1876,30 @@
 
     /**
      * Checks if a given number is an emergency number for the country that the user is in.
-     * @param context the specific context which the number should be checked against
-     * @param number the number to look up.
      *
+     * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
      * @return true if the specified number is an emergency number for the country the user
      * is currently in.
      */
     public static boolean isLocalEmergencyNumber(Context context, String number) {
-        return isLocalEmergencyNumberInternal(context, number, true /* useExactMatch */);
+        return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
+    }
+
+    /**
+     * Checks if a given number is an emergency number for the country that the user is in.
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
+     * @return true if the specified number is an emergency number for the country the user
+     * is currently in.
+     * @hide
+     */
+    public static boolean isLocalEmergencyNumber(Context context, long subId, String number) {
+        return isLocalEmergencyNumberInternal(subId, number,
+                                              context,
+                                              true /* useExactMatch */);
     }
 
     /**
@@ -1765,9 +1915,9 @@
      * This method is intended for internal use by the phone app when
      * deciding whether to allow ACTION_CALL intents from 3rd party apps
      * (where we're required to *not* allow emergency calls to be placed.)
-     * @param context the specific context which the number should be checked against
-     * @param number the number to look up.
      *
+     * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
      * @return true if the specified number is an emergency number for a local country, based on the
      *              CountryDetector.
      *
@@ -1775,14 +1925,44 @@
      * @hide
      */
     public static boolean isPotentialLocalEmergencyNumber(Context context, String number) {
-        return isLocalEmergencyNumberInternal(context, number, false /* useExactMatch */);
+        return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
+    }
+
+    /**
+     * Checks if a given number might *potentially* result in a call to an
+     * emergency service, for the country that the user is in. The current
+     * country is determined using the CountryDetector.
+     *
+     * Specifically, this method will return true if the specified number
+     * is an emergency number in the current country, *or* if the number
+     * simply starts with the same digits as any emergency number for the
+     * current country.
+     *
+     * This method is intended for internal use by the phone app when
+     * deciding whether to allow ACTION_CALL intents from 3rd party apps
+     * (where we're required to *not* allow emergency calls to be placed.)
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
+     * @return true if the specified number is an emergency number for a local country, based on the
+     *              CountryDetector.
+     *
+     * @hide
+     */
+    public static boolean isPotentialLocalEmergencyNumber(Context context, long subId,
+            String number) {
+        return isLocalEmergencyNumberInternal(subId, number,
+                                              context,
+                                              false /* useExactMatch */);
     }
 
     /**
      * Helper function for isLocalEmergencyNumber() and
      * isPotentialLocalEmergencyNumber().
-     * @param context the specific context which the number should be checked against
+     *
      * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
      * @param useExactMatch if true, consider a number to be an emergency
      *           number only if it *exactly* matches a number listed in
      *           the RIL / SIM.  If false, a number is considered to be an
@@ -1793,9 +1973,35 @@
      *              local country, based on the CountryDetector.
      *
      * @see android.location.CountryDetector
+     * @hide
      */
-    private static boolean isLocalEmergencyNumberInternal(Context context, String number,
-            boolean useExactMatch) {
+    private static boolean isLocalEmergencyNumberInternal(String number,
+                                                          Context context,
+                                                          boolean useExactMatch) {
+        return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context,
+                useExactMatch);
+    }
+
+    /**
+     * Helper function for isLocalEmergencyNumber() and
+     * isPotentialLocalEmergencyNumber().
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @param context the specific context which the number should be checked against
+     * @param useExactMatch if true, consider a number to be an emergency
+     *           number only if it *exactly* matches a number listed in
+     *           the RIL / SIM.  If false, a number is considered to be an
+     *           emergency number if it simply starts with the same digits
+     *           as any of the emergency numbers listed in the RIL / SIM.
+     *
+     * @return true if the specified number is an emergency number for a
+     *              local country, based on the CountryDetector.
+     * @hide
+     */
+    private static boolean isLocalEmergencyNumberInternal(long subId, String number,
+                                                          Context context,
+                                                          boolean useExactMatch) {
         String countryIso;
         CountryDetector detector = (CountryDetector) context.getSystemService(
                 Context.COUNTRY_DETECTOR);
@@ -1807,7 +2013,7 @@
             Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: "
                     + countryIso);
         }
-        return isEmergencyNumberInternal(number, countryIso, useExactMatch);
+        return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch);
     }
 
     /**
@@ -1821,10 +2027,26 @@
      * to read the VM number.
      */
     public static boolean isVoiceMailNumber(String number) {
+        return isVoiceMailNumber(SubscriptionManager.getDefaultSubId(), number);
+    }
+
+    /**
+     * isVoiceMailNumber: checks a given number against the voicemail
+     *   number provided by the RIL and SIM card. The caller must have
+     *   the READ_PHONE_STATE credential.
+     *
+     * @param subId the subscription id of the SIM.
+     * @param number the number to look up.
+     * @return true if the number is in the list of voicemail. False
+     * otherwise, including if the caller does not have the permission
+     * to read the VM number.
+     * @hide
+     */
+    public static boolean isVoiceMailNumber(long subId, String number) {
         String vmNumber;
 
         try {
-            vmNumber = TelephonyManager.getDefault().getVoiceMailNumber();
+            vmNumber = TelephonyManager.getDefault().getVoiceMailNumber(subId);
         } catch (SecurityException ex) {
             return false;
         }
@@ -2556,5 +2778,11 @@
         return true;
     }
 
+    /**
+     * Returns Default voice subscription Id.
+     */
+    private static long getDefaultVoiceSubId() {
+        return SubscriptionManager.getDefaultVoiceSubId();
+    }
     //==== End of utility methods used only in compareStrictly() =====
 }
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 59ec6f5..c8c3063 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -20,11 +20,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
 import android.telephony.CellLocation;
 import android.telephony.CellInfo;
+import android.telephony.VoLteServiceState;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
 
@@ -48,6 +50,7 @@
  * appropriate LISTEN_ flags.
  */
 public class PhoneStateListener {
+    private static final String TAG = "PhoneStateListener";
 
     /**
      * Stop listening for updates.
@@ -200,18 +203,42 @@
      */
     public static final int LISTEN_DATA_CONNECTION_REAL_TIME_INFO           = 0x00002000;
 
+    /**
+     * Listen for changes to LTE network state
+     *
+     * @see #onLteNetworkStateChanged
+     * @hide
+     */
+    public static final int LISTEN_VOLTE_STATE                              = 0x00004000;
+
+     /*
+     * Subscription used to listen to the phone state changes
+     * @hide
+     */
+    /** @hide */
+    protected long mSubId = 0;
+
     private final Handler mHandler;
 
     public PhoneStateListener() {
-        this(Looper.myLooper());
+        this(SubscriptionManager.DEFAULT_SUB_ID, Looper.myLooper());
+    }
+
+    /**
+     * @hide
+     */
+    public PhoneStateListener(long subId) {
+        this(subId, Looper.myLooper());
     }
 
     /** @hide */
-    public PhoneStateListener(Looper looper) {
+    public PhoneStateListener(long subId, Looper looper) {
+        Rlog.d(TAG, "ctor: subId=" + subId + " looper=" + looper);
+        mSubId = subId;
         mHandler = new Handler(looper) {
             public void handleMessage(Message msg) {
-                //Rlog.d("TelephonyRegistry", "what=0x" + Integer.toHexString(msg.what)
-                // + " msg=" + msg);
+                Rlog.d(TAG, "mSubId=" + mSubId + " what=0x" + Integer.toHexString(msg.what)
+                 + " msg=" + msg);
                 switch (msg.what) {
                     case LISTEN_SERVICE_STATE:
                         PhoneStateListener.this.onServiceStateChanged((ServiceState)msg.obj);
@@ -258,6 +285,9 @@
                         PhoneStateListener.this.onDataConnectionRealTimeInfoChanged(
                                 (DataConnectionRealTimeInfo)msg.obj);
                         break;
+                    case LISTEN_VOLTE_STATE:
+                        PhoneStateListener.this.onVoLteServiceStateChanged((VoLteServiceState)msg.obj);
+                        break;
                 }
             }
         };
@@ -417,6 +447,15 @@
     }
 
     /**
+     * Callback invoked when the service state of LTE network
+     * related to the VoLTE service has changed.
+     * @param stateInfo is the current LTE network information
+     * @hide
+     */
+    public void onVoLteServiceStateChanged(VoLteServiceState stateInfo) {
+    }
+
+    /**
      * The callback methods need to be called on the handler thread where
      * this object was created.  If the binder did that for us it'd be nice.
      */
@@ -484,5 +523,9 @@
             Message.obtain(mHandler, LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0,
                     dcRtInfo).sendToTarget();
         }
+
+        public void onVoLteServiceStateChanged(VoLteServiceState lteState) {
+            Message.obtain(mHandler, LISTEN_VOLTE_STATE, 0, 0, lteState).sendToTarget();
+        }
     };
 }
diff --git a/telephony/java/android/telephony/SubInfoRecord.aidl b/telephony/java/android/telephony/SubInfoRecord.aidl
new file mode 100755
index 0000000..a2de676
--- /dev/null
+++ b/telephony/java/android/telephony/SubInfoRecord.aidl
@@ -0,0 +1,19 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* 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.telephony;
+
+parcelable SubInfoRecord;
diff --git a/telephony/java/android/telephony/SubInfoRecord.java b/telephony/java/android/telephony/SubInfoRecord.java
new file mode 100644
index 0000000..ced8e2f
--- /dev/null
+++ b/telephony/java/android/telephony/SubInfoRecord.java
@@ -0,0 +1,115 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ *  A parcelable holder class of byte[] for ISms aidl implementation
+ *  @hide
+ */
+
+public class SubInfoRecord implements Parcelable {
+
+    public long mSubId;
+    public String mIccId;
+    public int mSlotId;
+    public String mDisplayName;
+    public int mNameSource;
+    public int mColor;
+    public String mNumber;
+    public int mDispalyNumberFormat;
+    public int mDataRoaming;
+    public int[] mSimIconRes;
+
+    public SubInfoRecord() {
+        this.mSubId = -1;
+        this.mIccId = "";
+        this.mSlotId = -1;
+        this.mDisplayName = "";
+        this.mNameSource = 0;
+        this.mColor = 0;
+        this.mNumber = "";
+        this.mDispalyNumberFormat = 0;
+        this.mDataRoaming = 0;
+        this.mSimIconRes = new int[2];
+    }
+
+
+    public SubInfoRecord(long subId, String iccId, int slotId, String displayname, int nameSource,
+            int mColor, String mNumber, int displayFormat, int roaming, int[] iconRes) {
+        this.mSubId = subId;
+        this.mIccId = iccId;
+        this.mSlotId = slotId;
+        this.mDisplayName = displayname;
+        this.mNameSource = nameSource;
+        this.mColor = mColor;
+        this.mNumber = mNumber;
+        this.mDispalyNumberFormat = displayFormat;
+        this.mDataRoaming = roaming;
+        this.mSimIconRes = iconRes;
+    }
+
+    public static final Parcelable.Creator<SubInfoRecord> CREATOR = new Parcelable.Creator<SubInfoRecord>() {
+        public SubInfoRecord createFromParcel(Parcel source) {
+            long mSubId = source.readLong();
+            String mIccId = source.readString();
+            int mSlotId = source.readInt();
+            String mDisplayName = source.readString();
+            int mNameSource = source.readInt();
+            int mColor = source.readInt();
+            String mNumber = source.readString();
+            int mDispalyNumberFormat = source.readInt();
+            int mDataRoaming = source.readInt();
+            int[] iconRes = new int[2];
+            source.readIntArray(iconRes);
+
+            return new SubInfoRecord(mSubId, mIccId, mSlotId, mDisplayName, mNameSource, mColor, mNumber,
+                mDispalyNumberFormat, mDataRoaming, iconRes);
+        }
+
+        public SubInfoRecord[] newArray(int size) {
+            return new SubInfoRecord[size];
+        }
+    };
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mSubId);
+        dest.writeString(mIccId);
+        dest.writeInt(mSlotId);
+        dest.writeString(mDisplayName);
+        dest.writeInt(mNameSource);
+        dest.writeInt(mColor);
+        dest.writeString(mNumber);
+        dest.writeInt(mDispalyNumberFormat);
+        dest.writeInt(mDataRoaming);
+        dest.writeIntArray(mSimIconRes);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public String toString() {
+        return "{mSubId=" + mSubId + ", mIccId=" + mIccId + " mSlotId=" + mSlotId
+                + " mDisplayName=" + mDisplayName + " mNameSource=" + mNameSource
+                + " mColor=" + mColor + " mNumber=" + mNumber
+                + " mDispalyNumberFormat=" + mDispalyNumberFormat + " mDataRoaming=" + mDataRoaming
+                + " mSimIconRes=" + mSimIconRes + "}";
+    }
+}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
new file mode 100644
index 0000000..79e9fd5
--- /dev/null
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -0,0 +1,712 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* 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.telephony;
+
+import static android.Manifest.permission.READ_PHONE_STATE;
+
+import android.app.ActivityManagerNative;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.UserHandle;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.telephony.Rlog;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+
+import com.android.internal.telephony.ISub;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+/**
+ *@hide
+ */
+public class SubscriptionManager implements BaseColumns {
+    private static final String LOG_TAG = "SUB";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    // An invalid subscription identifier
+    public static final long INVALID_SUB_ID = Long.MAX_VALUE;
+
+    // The default subscription identifier
+    public static final long DEFAULT_SUB_ID = Long.MAX_VALUE - 1;
+
+    public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
+
+    public static final int DEFAULT_INT_VALUE = -100;
+
+    public static final String DEFAULT_STRING_VALUE = "N/A";
+
+    public static final int EXTRA_VALUE_NEW_SIM = 1;
+    public static final int EXTRA_VALUE_REMOVE_SIM = 2;
+    public static final int EXTRA_VALUE_REPOSITION_SIM = 3;
+    public static final int EXTRA_VALUE_NOCHANGE = 4;
+
+    public static final String INTENT_KEY_DETECT_STATUS = "simDetectStatus";
+    public static final String INTENT_KEY_SIM_COUNT = "simCount";
+    public static final String INTENT_KEY_NEW_SIM_SLOT = "newSIMSlot";
+    public static final String INTENT_KEY_NEW_SIM_STATUS = "newSIMStatus";
+
+    /**
+     * The ICC ID of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String ICC_ID = "icc_id";
+
+    /**
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String SIM_ID = "sim_id";
+
+    public static final int SIM_NOT_INSERTED = -1;
+
+    /**
+     * The display name of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String DISPLAY_NAME = "display_name";
+
+    public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName;
+
+    /**
+     * The display name source of a SIM.
+     * <P>Type: INT (int)</P>
+     */
+    public static final String NAME_SOURCE = "name_source";
+
+    public static final int DEFAULT_SOURCE = 0;
+
+    public static final int SIM_SOURCE = 1;
+
+    public static final int USER_INPUT = 2;
+
+    /**
+     * The color of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String COLOR = "color";
+
+    public static final int COLOR_1 = 0;
+
+    public static final int COLOR_2 = 1;
+
+    public static final int COLOR_3 = 2;
+
+    public static final int COLOR_4 = 3;
+
+    public static final int COLOR_DEFAULT = COLOR_1;
+
+    /**
+     * The phone number of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String NUMBER = "number";
+
+    /**
+     * The number display format of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
+
+    public static final int DISPALY_NUMBER_NONE = 0;
+
+    public static final int DISPLAY_NUMBER_FIRST = 1;
+
+    public static final int DISPLAY_NUMBER_LAST = 2;
+
+    public static final int DISLPAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
+
+    /**
+     * Permission for data roaming of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String DATA_ROAMING = "data_roaming";
+
+    public static final int DATA_ROAMING_ENABLE = 1;
+
+    public static final int DATA_ROAMING_DISABLE = 0;
+
+    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
+
+    private static final int RES_TYPE_BACKGROUND_DARK = 0;
+
+    private static final int RES_TYPE_BACKGROUND_LIGHT = 1;
+
+    private static final int[] sSimBackgroundDarkRes = setSimResource(RES_TYPE_BACKGROUND_DARK);
+
+    private static final int[] sSimBackgroundLightRes = setSimResource(RES_TYPE_BACKGROUND_LIGHT);
+
+    private static HashMap<Integer, Long> mSimInfo = new HashMap<Integer, Long>();
+
+    public SubscriptionManager() {
+        if (DBG) logd("SubscriptionManager created");
+    }
+
+    /**
+     * Get the SubInfoRecord according to an index
+     * @param context Context provided by caller
+     * @param subId The unique SubInfoRecord index in database
+     * @return SubInfoRecord, maybe null
+     */
+    public static SubInfoRecord getSubInfoUsingSubId(Context context, long subId) {
+        if (VDBG) logd("[getSubInfoUsingSubIdx]+ subId:" + subId);
+        if (subId <= 0) {
+            if (VDBG) logd("[getSubInfoUsingSubIdx]- subId <= 0");
+            return null;
+        }
+
+        SubInfoRecord subInfo = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                subInfo = iSub.getSubInfoUsingSubId(subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return subInfo;
+
+    }
+
+    /**
+     * Get the SubInfoRecord according to an IccId
+     * @param context Context provided by caller
+     * @param iccId the IccId of SIM card
+     * @return SubInfoRecord, maybe null
+     */
+    public static List<SubInfoRecord> getSubInfoUsingIccId(Context context, String iccId) {
+        if (VDBG) logd("[getSubInfoUsingIccId]+ iccId=" + iccId);
+        if (iccId == null) {
+            logd("[getSubInfoUsingIccId]- null iccid");
+            return null;
+        }
+
+        List<SubInfoRecord> result = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getSubInfoUsingIccId(iccId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the SubInfoRecord according to slotId
+     * @param context Context provided by caller
+     * @param slotId the slot which the SIM is inserted
+     * @return SubInfoRecord, maybe null
+     */
+    public static List<SubInfoRecord> getSubInfoUsingSlotId(Context context, int slotId) {
+        if (VDBG) logd("[getSubInfoUsingSlotId]- slotId=" + slotId);
+        if (slotId < 0) {
+            logd("[getSubInfoUsingSlotId]- return null, slotId < 0");
+            return null;
+        }
+
+        List<SubInfoRecord> result = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getSubInfoUsingSlotId(slotId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    /**
+     * Get all the SubInfoRecord(s) in subinfo database
+     * @param context Context provided by caller
+     * @return Array list of all SubInfoRecords in database, include thsoe that were inserted before
+     */
+    public static List<SubInfoRecord> getAllSubInfoList(Context context) {
+        if (VDBG) logd("[getAllSubInfoList]+");
+
+        List<SubInfoRecord> result = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getAllSubInfoList();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the SubInfoRecord(s) of the currently inserted SIM(s)
+     * @param context Context provided by caller
+     * @return Array list of currently inserted SubInfoRecord(s)
+     */
+    public static List<SubInfoRecord> getActivatedSubInfoList(Context context) {
+        if (VDBG) logd("[getActivatedSubInfoList]+");
+
+        List<SubInfoRecord> result = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getActivatedSubInfoList();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the SUB count of all SUB(s) in subinfo database
+     * @param context Context provided by caller
+     * @return all SIM count in database, include what was inserted before
+     */
+    public static int getAllSubInfoCount(Context context) {
+        if (VDBG) logd("[getAllSubInfoCount]+");
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getAllSubInfoCount();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    /**
+     * Add a new SubInfoRecord to subinfo database if needed
+     * @param context Context provided by caller
+     * @param iccId the IccId of the SIM card
+     * @param slotId the slot which the SIM is inserted
+     * @return the URL of the newly created row or the updated row
+     */
+    public static Uri addSubInfoRecord(Context context, String iccId, int slotId) {
+        if (VDBG) logd("[addSubInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
+        if (iccId == null) {
+            logd("[addSubInfoRecord]- null iccId");
+        }
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                // FIXME: This returns 1 on success, 0 on error should should we return it?
+                iSub.addSubInfoRecord(iccId, slotId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        // FIXME: Always returns null?
+        return null;
+
+    }
+
+    /**
+     * Set SIM color by simInfo index
+     * @param context Context provided by caller
+     * @param color the color of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public static int setColor(Context context, int color, long subId) {
+        if (VDBG) logd("[setColor]+ color:" + color + " subId:" + subId);
+        int size = sSimBackgroundDarkRes.length;
+        if (subId <= 0 || color < 0 || color >= size) {
+            logd("[setColor]- fail");
+            return -1;
+        }
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.setColor(color, subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Set display name by simInfo index
+     * @param context Context provided by caller
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public static int setDisplayName(Context context, String displayName, long subId) {
+        return setDisplayName(context, displayName, subId, -1);
+    }
+
+    /**
+     * Set display name by simInfo index with name source
+     * @param context Context provided by caller
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @param nameSource, 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT
+     * @return the number of records updated
+     */
+    public static int setDisplayName(Context context, String displayName, long subId, long nameSource) {
+        if (VDBG) logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId + " nameSource:" + nameSource);
+        if (subId <= 0) {
+            logd("[setDisplayName]- fail");
+            return -1;
+        }
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.setDisplayNameUsingSrc(displayName, subId, nameSource);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Set phone number by subId
+     * @param context Context provided by caller
+     * @param number the phone number of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public static int setDispalyNumber(Context context, String number, long subId) {
+        if (VDBG) logd("[setDispalyNumber]+ number:" + number + " subId:" + subId);
+        if (number == null || subId <= 0) {
+            logd("[setDispalyNumber]- fail");
+            return -1;
+        }
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.setDispalyNumber(number, subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Set number display format. 0: none, 1: the first four digits, 2: the last four digits
+     * @param context Context provided by caller
+     * @param format the display format of phone number
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public static int setDisplayNumberFormat(Context context, int format, long subId) {
+        if (VDBG) logd("[setDisplayNumberFormat]+ format:" + format + " subId:" + subId);
+        if (format < 0 || subId <= 0) {
+            logd("[setDisplayNumberFormat]- fail, return -1");
+            return -1;
+        }
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.setDisplayNumberFormat(format, subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Set data roaming by simInfo index
+     * @param context Context provided by caller
+     * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public static int setDataRoaming(Context context, int roaming, long subId) {
+        if (VDBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
+        if (roaming < 0 || subId <= 0) {
+            logd("[setDataRoaming]- fail");
+            return -1;
+        }
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.setDataRoaming(roaming, subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+    }
+
+    public static int getSlotId(long subId) {
+        if (VDBG) logd("[getSlotId]+ subId:" + subId);
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getSlotId(subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return result;
+
+    }
+
+    public static long[] getSubId(int slotId) {
+        if (VDBG) logd("[getSubId]+ slotId:" + slotId);
+
+        long[] subId = null;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                subId = iSub.getSubId(slotId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return subId;
+    }
+
+    public static int getPhoneId(long subId) {
+        if (VDBG) logd("[getPhoneId]+ subId=" + subId);
+
+        int result = 0;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                result = iSub.getPhoneId(subId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (VDBG) logd("[getPhoneId]- phonId=" + result);
+        return result;
+
+    }
+
+    private static int[] setSimResource(int type) {
+        int[] simResource = null;
+
+        switch (type) {
+            case RES_TYPE_BACKGROUND_DARK:
+                simResource = new int[] {
+                    com.android.internal.R.drawable.sim_dark_blue,
+                    com.android.internal.R.drawable.sim_dark_orange,
+                    com.android.internal.R.drawable.sim_dark_green,
+                    com.android.internal.R.drawable.sim_dark_purple
+                };
+                break;
+            case RES_TYPE_BACKGROUND_LIGHT:
+                simResource = new int[] {
+                    com.android.internal.R.drawable.sim_light_blue,
+                    com.android.internal.R.drawable.sim_light_orange,
+                    com.android.internal.R.drawable.sim_light_green,
+                    com.android.internal.R.drawable.sim_light_purple
+                };
+                break;
+        }
+
+        return simResource;
+    }
+
+    private static void logd(String msg) {
+        Rlog.d(LOG_TAG, "[SubManager] " + msg);
+    }
+
+    public static long normalizeSubId(long subId) {
+        long retVal = (subId == DEFAULT_SUB_ID) ? getDefaultSubId() : subId;
+        Rlog.d(LOG_TAG, "[SubManager] normalizeSubId subId=" + retVal);
+        return retVal;
+    }
+
+    public static boolean validSubId(long subId) {
+        return (subId != DEFAULT_SUB_ID) && (subId != -1);
+    }
+
+    /**
+     * @return the "system" defaultSubId on a voice capable device this
+     * will be getDefaultVoiceSubId() and on a data only device it will be
+     * getDefaultDataSubId().
+     */
+    public static long getDefaultSubId() {
+        long subId = 1;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                subId = iSub.getDefaultSubId();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (VDBG) logd("getDefaultSubId=" + subId);
+        return subId;
+    }
+
+    public static long getDefaultVoiceSubId() {
+        long subId = 1;
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                subId = iSub.getDefaultVoiceSubId();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        if (VDBG) logd("getDefaultSubId, sub id = " + subId);
+        return subId;
+    }
+
+    public static void setDefaultVoiceSubId(long subId) {
+        if (VDBG) logd("setDefaultVoiceSubId sub id = " + subId);
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                iSub.setDefaultVoiceSubId(subId);
+            }
+        } catch (RemoteException ex) {
+         // ignore it
+        }
+    }
+
+    public static long getPreferredSmsSubId() {
+        // FIXME add framework support to get the preferred sub
+        return getDefaultSubId();
+    }
+
+    public static long getPreferredDataSubId() {
+        // FIXME add framework support to get the preferred sub
+        return getDefaultSubId();
+    }
+
+    public static long getDefaultDataSubId() {
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                return iSub.getDefaultDataSubId();
+            } else {
+                return -1;
+            }
+        } catch (RemoteException ex) {
+            return -1;
+        }
+    }
+
+    public static void setDefaultDataSubId(long subId) {
+        if (VDBG) logd("setDataSubscription sub id = " + subId);
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                iSub.setDefaultDataSubId(subId);
+            }
+        } catch (RemoteException ex) {
+         // ignore it
+        }
+    }
+
+    public static void clearSubInfo()
+    {
+        if (VDBG) logd("[clearSubInfo]+");
+
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                iSub.clearSubInfo();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return;
+    }
+
+    public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) {
+        long [] subId = SubscriptionManager.getSubId(phoneId);
+        if ((subId != null) && (subId.length >= 1)) {
+            putPhoneIdAndSubIdExtra(intent, phoneId, subId[0]);
+        } else {
+            logd("putPhoneIdAndSubIdExtra: no valid subs");
+        }
+    }
+
+    public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, long subId) {
+        if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
+        intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); //FIXME: RENAME TO PHONE_ID_KEY ??
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+    }
+}
+
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 949bc5d..124a8ec 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19,15 +19,12 @@
 import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
 import android.content.Context;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.telephony.Rlog;
+import android.telecomm.Subscription;
 import android.util.Log;
 
 import com.android.internal.telecomm.ITelecommService;
@@ -40,7 +37,6 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -86,6 +82,22 @@
 
     private final Context mContext;
 
+    private static String multiSimConfig =
+            SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
+
+    /** Enum indicating multisim variants
+     *  DSDS - Dual SIM Dual Standby
+     *  DSDA - Dual SIM Dual Active
+     *  TSTS - Triple SIM Triple Standby
+     **/
+    /** @hide */
+    public enum MultiSimVariants {
+        DSDS,
+        DSDA,
+        TSTS,
+        UNKNOWN
+    };
+
     /** @hide */
     public TelephonyManager(Context context) {
         Context appContext = context.getApplicationContext();
@@ -114,11 +126,61 @@
         return sInstance;
     }
 
+
+    /**
+     * Returns the multi SIM variant
+     * Returns DSDS for Dual SIM Dual Standby
+     * Returns DSDA for Dual SIM Dual Active
+     * Returns TSTS for Triple SIM Triple Standby
+     * Returns UNKNOWN for others
+     */
+    /** {@hide} */
+    public MultiSimVariants getMultiSimConfiguration() {
+        String mSimConfig =
+            SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
+        if (mSimConfig.equals("dsds")) {
+            return MultiSimVariants.DSDS;
+        } else if (mSimConfig.equals("dsda")) {
+            return MultiSimVariants.DSDA;
+        } else if (mSimConfig.equals("tsts")) {
+            return MultiSimVariants.TSTS;
+        } else {
+            return MultiSimVariants.UNKNOWN;
+        }
+    }
+
+
+    /**
+     * Returns the number of phones available.
+     * Returns 1 for Single standby mode (Single SIM functionality)
+     * Returns 2 for Dual standby mode.(Dual SIM functionality)
+     */
+    /** {@hide} */
+    public int getPhoneCount() {
+        int phoneCount = 1;
+        switch (getMultiSimConfiguration()) {
+            case DSDS:
+            case DSDA:
+                phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+                break;
+            case TSTS:
+                phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM;
+                break;
+        }
+        return phoneCount;
+    }
+
     /** {@hide} */
     public static TelephonyManager from(Context context) {
         return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
     }
 
+    /** {@hide} */
+    public boolean isMultiSimEnabled() {
+        return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") ||
+            multiSimConfig.equals("tsts"));
+    }
+
     //
     // Broadcast Intent actions
     //
@@ -236,6 +298,17 @@
     public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
 
     /**
+     * The lookup key used with an {@link android.content.Intent#ACTION_CALL} or
+     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} for a {@link Subscription}
+     * object indicating a preference when making a phone connection.
+     *
+     * <p class="note">
+     * Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     */
+    public static final String EXTRA_SUBSCRIPTION = "subscription";
+
+    /**
      * Broadcast intent action indicating that a precise call state
      * (cellular) on the device has changed.
      *
@@ -529,8 +602,23 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getDeviceId() {
+        return getDeviceId(getDefaultSim());
+    }
+
+    /**
+     * Returns the unique device ID of a subscription, for example, the IMEI for
+     * GSM and the MEID for CDMA phones. Return null if device ID is not available.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param slotId of which deviceID is returned
+     */
+    /** {@hide} */
+    public String getDeviceId(int slotId) {
+        long[] subId = SubscriptionManager.getSubId(slotId);
         try {
-            return getSubscriberInfo().getDeviceId();
+            return getSubscriberInfo().getDeviceIdUsingSubId(subId[0]);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -578,8 +666,23 @@
      * @hide
      */
     public void enableLocationUpdates() {
+            enableLocationUpdates(getDefaultSubscription());
+    }
+
+    /**
+     * Enables location update notifications for a subscription.
+     * {@link PhoneStateListener#onCellLocationChanged
+     * PhoneStateListener.onCellLocationChanged} will be called on location updates.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#CONTROL_LOCATION_UPDATES
+     * CONTROL_LOCATION_UPDATES}
+     *
+     * @param subId for which the location updates are enabled
+     */
+    /** @hide */
+    public void enableLocationUpdates(long subId) {
         try {
-            getITelephony().enableLocationUpdates();
+            getITelephony().enableLocationUpdatesUsingSubId(subId);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -595,8 +698,13 @@
      * @hide
      */
     public void disableLocationUpdates() {
+            disableLocationUpdates(getDefaultSubscription());
+    }
+
+    /** @hide */
+    public void disableLocationUpdates(long subId) {
         try {
-            getITelephony().disableLocationUpdates();
+            getITelephony().disableLocationUpdatesUsingSubId(subId);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -644,22 +752,37 @@
      * {@hide}
      */
     public int getCurrentPhoneType() {
+        return getCurrentPhoneType(getDefaultSubscription());
+    }
+
+    /**
+     * Returns a constant indicating the device phone type for a subscription.
+     *
+     * @see #PHONE_TYPE_NONE
+     * @see #PHONE_TYPE_GSM
+     * @see #PHONE_TYPE_CDMA
+     *
+     * @param subId for which phone type is returned
+     */
+    /** {@hide} */
+    public int getCurrentPhoneType(long subId) {
+
         try{
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getActivePhoneType();
+                return telephony.getActivePhoneTypeUsingSubId(subId);
             } else {
                 // This can happen when the ITelephony interface is not up yet.
-                return getPhoneTypeFromProperty();
+                return getPhoneTypeFromProperty(subId);
             }
         } catch (RemoteException ex) {
             // This shouldn't happen in the normal case, as a backup we
             // read from the system property.
-            return getPhoneTypeFromProperty();
+            return getPhoneTypeFromProperty(subId);
         } catch (NullPointerException ex) {
             // This shouldn't happen in the normal case, as a backup we
             // read from the system property.
-            return getPhoneTypeFromProperty();
+            return getPhoneTypeFromProperty(subId);
         }
     }
 
@@ -680,20 +803,35 @@
     }
 
     private int getPhoneTypeFromProperty() {
-        int type =
-            SystemProperties.getInt(TelephonyProperties.CURRENT_ACTIVE_PHONE,
-                    getPhoneTypeFromNetworkType());
-        return type;
+        return getPhoneTypeFromProperty(getDefaultSubscription());
+    }
+
+    /** {@hide} */
+    private int getPhoneTypeFromProperty(long subId) {
+        String type =
+            getTelephonyProperty
+                (TelephonyProperties.CURRENT_ACTIVE_PHONE, subId, null);
+        if (type != null) {
+            return (Integer.parseInt(type));
+        } else {
+            return getPhoneTypeFromNetworkType(subId);
+        }
     }
 
     private int getPhoneTypeFromNetworkType() {
+        return getPhoneTypeFromNetworkType(getDefaultSubscription());
+    }
+
+    /** {@hide} */
+    private int getPhoneTypeFromNetworkType(long subId) {
         // When the system property CURRENT_ACTIVE_PHONE, has not been set,
         // use the system property for default network type.
         // This is a fail safe, and can only happen at first boot.
-        int mode = SystemProperties.getInt("ro.telephony.default_network", -1);
-        if (mode == -1)
-            return PHONE_TYPE_NONE;
-        return getPhoneType(mode);
+        String mode = getTelephonyProperty("ro.telephony.default_network", subId, null);
+        if (mode != null) {
+            return TelephonyManager.getPhoneType(Integer.parseInt(mode));
+        }
+        return TelephonyManager.PHONE_TYPE_NONE;
     }
 
     /**
@@ -828,7 +966,23 @@
      * on a CDMA network).
      */
     public String getNetworkOperatorName() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ALPHA);
+        return getNetworkOperatorName(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the alphabetic name of current registered operator
+     * for a particular subscription.
+     * <p>
+     * Availability: Only when user is registered to a network. Result may be
+     * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
+     * on a CDMA network).
+     * @param subId
+     */
+    /** {@hide} */
+    public String getNetworkOperatorName(long subId) {
+
+        return getTelephonyProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
+                subId, "");
     }
 
     /**
@@ -839,17 +993,48 @@
      * on a CDMA network).
      */
     public String getNetworkOperator() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
+        return getNetworkOperator(getDefaultSubscription());
     }
 
     /**
+     * Returns the numeric name (MCC+MNC) of current registered operator
+     * for a particular subscription.
+     * <p>
+     * Availability: Only when user is registered to a network. Result may be
+     * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
+     * on a CDMA network).
+     *
+     * @param subId
+     */
+    /** {@hide} */
+   public String getNetworkOperator(long subId) {
+
+        return getTelephonyProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC,
+                subId, "");
+     }
+
+    /**
      * Returns true if the device is considered roaming on the current
      * network, for GSM purposes.
      * <p>
      * Availability: Only when user registered to a network.
      */
     public boolean isNetworkRoaming() {
-        return "true".equals(SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING));
+        return isNetworkRoaming(getDefaultSubscription());
+    }
+
+    /**
+     * Returns true if the device is considered roaming on the current
+     * network for a subscription.
+     * <p>
+     * Availability: Only when user registered to a network.
+     *
+     * @param subId
+     */
+    /** {@hide} */
+    public boolean isNetworkRoaming(long subId) {
+        return "true".equals(getTelephonyProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING,
+                subId, null));
     }
 
     /**
@@ -861,7 +1046,23 @@
      * on a CDMA network).
      */
     public String getNetworkCountryIso() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
+        return getNetworkCountryIso(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the ISO country code equivalent of the current registered
+     * operator's MCC (Mobile Country Code) of a subscription.
+     * <p>
+     * Availability: Only when user is registered to a network. Result may be
+     * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
+     * on a CDMA network).
+     *
+     * @param subId for which Network CountryIso is returned
+     */
+    /** {@hide} */
+    public String getNetworkCountryIso(long subId) {
+        return getTelephonyProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
+                subId, "");
     }
 
     /** Network type is unknown */
@@ -908,6 +1109,49 @@
 
     /**
      * Returns a constant indicating the radio technology (network type)
+     * currently in use on the device for a subscription.
+     * @return the network type
+     *
+     * @param subId for which network type is returned
+     *
+     * @see #NETWORK_TYPE_UNKNOWN
+     * @see #NETWORK_TYPE_GPRS
+     * @see #NETWORK_TYPE_EDGE
+     * @see #NETWORK_TYPE_UMTS
+     * @see #NETWORK_TYPE_HSDPA
+     * @see #NETWORK_TYPE_HSUPA
+     * @see #NETWORK_TYPE_HSPA
+     * @see #NETWORK_TYPE_CDMA
+     * @see #NETWORK_TYPE_EVDO_0
+     * @see #NETWORK_TYPE_EVDO_A
+     * @see #NETWORK_TYPE_EVDO_B
+     * @see #NETWORK_TYPE_1xRTT
+     * @see #NETWORK_TYPE_IDEN
+     * @see #NETWORK_TYPE_LTE
+     * @see #NETWORK_TYPE_EHRPD
+     * @see #NETWORK_TYPE_HSPAP
+     */
+    /** {@hide} */
+   public int getNetworkType(long subId) {
+       try {
+           ITelephony telephony = getITelephony();
+           if (telephony != null) {
+               return telephony.getNetworkTypeUsingSubId(subId);
+           } else {
+               // This can happen when the ITelephony interface is not up yet.
+               return NETWORK_TYPE_UNKNOWN;
+           }
+       } catch(RemoteException ex) {
+           // This shouldn't happen in the normal case
+           return NETWORK_TYPE_UNKNOWN;
+       } catch (NullPointerException ex) {
+           // This could happen before phone restarts due to crashing
+           return NETWORK_TYPE_UNKNOWN;
+       }
+   }
+
+    /**
+     * Returns a constant indicating the radio technology (network type)
      * currently in use on the device for data transmission.
      * @return the network type
      *
@@ -931,10 +1175,22 @@
      * @hide
      */
     public int getDataNetworkType() {
+        return getDataNetworkType(getDefaultSubscription());
+    }
+
+    /**
+     * Returns a constant indicating the radio technology (network type)
+     * currently in use on the device for data transmission for a subscription
+     * @return the network type
+     *
+     * @param subId for which network type is returned
+     */
+    /** {@hide} */
+    public int getDataNetworkType(long subId) {
         try{
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getDataNetworkType();
+                return telephony.getDataNetworkTypeUsingSubId(subId);
             } else {
                 // This can happen when the ITelephony interface is not up yet.
                 return NETWORK_TYPE_UNKNOWN;
@@ -954,10 +1210,19 @@
      * @hide
      */
     public int getVoiceNetworkType() {
+        return getVoiceNetworkType(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the NETWORK_TYPE_xxxx for voice for a subId
+     *
+     */
+    /** {@hide} */
+    public int getVoiceNetworkType(long subId) {
         try{
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getVoiceNetworkType();
+                return telephony.getVoiceNetworkTypeUsingSubId(subId);
             } else {
                 // This can happen when the ITelephony interface is not up yet.
                 return NETWORK_TYPE_UNKNOWN;
@@ -1023,6 +1288,13 @@
         return getNetworkTypeName(getNetworkType());
     }
 
+    /**
+     * Returns a string representation of the radio technology (network type)
+     * currently in use on the device.
+     * @param subId for which network type is returned
+     * @return the name of the radio technology
+     *
+     */
     /** {@hide} */
     public static String getNetworkTypeName(int type) {
         switch (type) {
@@ -1093,8 +1365,20 @@
      * @return true if a ICC card is present
      */
     public boolean hasIccCard() {
+        return hasIccCard(getDefaultSim());
+    }
+
+    /**
+     * @return true if a ICC card is present for a subscription
+     *
+     * @param slotId for which icc card presence is checked
+     */
+    /** {@hide} */
+    // FIXME Input argument slotId should be of type int
+    public boolean hasIccCard(long slotId) {
+
         try {
-            return getITelephony().hasIccCard();
+            return getITelephony().hasIccCardUsingSlotId(slotId);
         } catch (RemoteException ex) {
             // Assume no ICC card if remote exception which shouldn't happen
             return false;
@@ -1117,7 +1401,33 @@
      * @see #SIM_STATE_CARD_IO_ERROR
      */
     public int getSimState() {
-        String prop = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_STATE);
+        return getSimState(getDefaultSim());
+    }
+
+    /**
+     * Returns a constant indicating the state of the
+     * device SIM card in a slot.
+     *
+     * @param slotId
+     *
+     * @see #SIM_STATE_UNKNOWN
+     * @see #SIM_STATE_ABSENT
+     * @see #SIM_STATE_PIN_REQUIRED
+     * @see #SIM_STATE_PUK_REQUIRED
+     * @see #SIM_STATE_NETWORK_LOCKED
+     * @see #SIM_STATE_READY
+     */
+    /** {@hide} */
+    // FIXME the argument to pass is subId ??
+    public int getSimState(int slotId) {
+        long[] subId = SubscriptionManager.getSubId(slotId);
+        if (subId == null) {
+            return SIM_STATE_ABSENT;
+        }
+        // FIXME Do not use a property to determine SIM_STATE, call
+        // appropriate method on some object.
+        String prop =
+            getTelephonyProperty(TelephonyProperties.PROPERTY_SIM_STATE, subId[0], "");
         if ("ABSENT".equals(prop)) {
             return SIM_STATE_ABSENT;
         }
@@ -1150,7 +1460,27 @@
      * @see #getSimState
      */
     public String getSimOperator() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC);
+        long subId = getDefaultSubscription();
+        Rlog.d(TAG, "getSimOperator(): default subId=" + subId);
+        return getSimOperator(subId);
+    }
+
+    /**
+     * Returns the MCC+MNC (mobile country code + mobile network code) of the
+     * provider of the SIM for a particular subscription. 5 or 6 decimal digits.
+     * <p>
+     * Availability: SIM state must be {@link #SIM_STATE_READY}
+     *
+     * @see #getSimState
+     *
+     * @param subId for which SimOperator is returned
+     */
+    /** {@hide} */
+    public String getSimOperator(long subId) {
+        String operator = getTelephonyProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC,
+                subId, "");
+        Rlog.d(TAG, "getSimOperator: subId=" + subId + " operator=" + operator);
+        return operator;
     }
 
     /**
@@ -1161,14 +1491,40 @@
      * @see #getSimState
      */
     public String getSimOperatorName() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA);
+        return getSimOperatorName(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the Service Provider Name (SPN).
+     * <p>
+     * Availability: SIM state must be {@link #SIM_STATE_READY}
+     *
+     * @see #getSimState
+     *
+     * @param subId for which SimOperatorName is returned
+     */
+    /** {@hide} */
+    public String getSimOperatorName(long subId) {
+        return getTelephonyProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA,
+                subId, "");
     }
 
     /**
      * Returns the ISO country code equivalent for the SIM provider's country code.
      */
     public String getSimCountryIso() {
-        return SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY);
+        return getSimCountryIso(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the ISO country code equivalent for the SIM provider's country code.
+     *
+     * @param subId for which SimCountryIso is returned
+     */
+    /** {@hide} */
+    public String getSimCountryIso(long subId) {
+        return getTelephonyProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
+                subId, "");
     }
 
     /**
@@ -1179,8 +1535,21 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getSimSerialNumber() {
+         return getSimSerialNumber(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the serial number for the given subscription, if applicable. Return null if it is
+     * unavailable.
+     * <p>
+     * @param subId for which Sim Serial number is returned
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     */
+    /** {@hide} */
+    public String getSimSerialNumber(long subId) {
         try {
-            return getSubscriberInfo().getIccSerialNumber();
+            return getSubscriberInfo().getIccSerialNumberUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1200,8 +1569,23 @@
      * @hide
      */
     public int getLteOnCdmaMode() {
+        return getLteOnCdmaMode(getDefaultSubscription());
+    }
+
+    /**
+     * Return if the current radio is LTE on CDMA for Subscription. This
+     * is a tri-state return value as for a period of time
+     * the mode may be unknown.
+     *
+     * @param subId for which radio is LTE on CDMA is returned
+     * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE}
+     * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
+     *
+     */
+    /** {@hide} */
+    public int getLteOnCdmaMode(long subId) {
         try {
-            return getITelephony().getLteOnCdmaMode();
+            return getITelephony().getLteOnCdmaModeUsingSubId(subId);
         } catch (RemoteException ex) {
             // Assume no ICC card if remote exception which shouldn't happen
             return PhoneConstants.LTE_ON_CDMA_UNKNOWN;
@@ -1225,8 +1609,23 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getSubscriberId() {
+        return getSubscriberId(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the unique subscriber ID, for example, the IMSI for a GSM phone
+     * for a subscription.
+     * Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param subId whose subscriber id is returned
+     */
+    /** {@hide} */
+    public String getSubscriberId(long subId) {
         try {
-            return getSubscriberInfo().getSubscriberId();
+            return getSubscriberInfo().getSubscriberIdUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1254,6 +1653,27 @@
     }
 
     /**
+     * Returns the Group Identifier Level1 for a GSM phone for a particular subscription.
+     * Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param subscription whose subscriber id is returned
+     */
+    /** {@hide} */
+    public String getGroupIdLevel1(long subId) {
+        try {
+            return getSubscriberInfo().getGroupIdLevel1UsingSubId(subId);
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
      * Returns the phone number string for line 1, for example, the MSISDN
      * for a GSM phone. Return null if it is unavailable.
      * <p>
@@ -1261,8 +1681,22 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getLine1Number() {
+        return getLine1Number(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the phone number string for line 1, for example, the MSISDN
+     * for a GSM phone for a particular subscription. Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param subId whose phone number for line 1 is returned
+     */
+    /** {@hide} */
+    public String getLine1Number(long subId) {
         try {
-            return getSubscriberInfo().getLine1Number();
+            return getSubscriberInfo().getLine1NumberUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1281,8 +1715,23 @@
      * nobody seems to call this.
      */
     public String getLine1AlphaTag() {
+        return getLine1AlphaTag(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the alphabetic identifier associated with the line 1 number
+     * for a subscription.
+     * Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * @param subId whose alphabetic identifier associated with line 1 is returned
+     * nobody seems to call this.
+     */
+    /** {@hide} */
+    public String getLine1AlphaTag(long subId) {
         try {
-            return getSubscriberInfo().getLine1AlphaTag();
+            return getSubscriberInfo().getLine1AlphaTagUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1301,8 +1750,22 @@
      * @hide
      */
     public String getMsisdn() {
+        return getMsisdn(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the MSISDN string.
+     * for a GSM phone. Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param subId for which msisdn is returned
+     */
+    /** {@hide} */
+    public String getMsisdn(long subId) {
         try {
-            return getSubscriberInfo().getMsisdn();
+            return getSubscriberInfo().getMsisdnUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1318,8 +1781,21 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getVoiceMailNumber() {
+        return getVoiceMailNumber(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the voice mail number for a subscription.
+     * Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * @param subId whose voice mail number is returned
+     */
+    /** {@hide} */
+    public String getVoiceMailNumber(long subId) {
         try {
-            return getSubscriberInfo().getVoiceMailNumber();
+            return getSubscriberInfo().getVoiceMailNumberUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1337,8 +1813,21 @@
      * @hide
      */
     public String getCompleteVoiceMailNumber() {
+        return getCompleteVoiceMailNumber(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the complete voice mail number. Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#CALL_PRIVILEGED CALL_PRIVILEGED}
+     *
+     * @param subId
+     */
+    /** {@hide} */
+    public String getCompleteVoiceMailNumber(long subId) {
         try {
-            return getSubscriberInfo().getCompleteVoiceMailNumber();
+            return getSubscriberInfo().getCompleteVoiceMailNumberUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1355,8 +1844,20 @@
      * @hide
      */
     public int getVoiceMessageCount() {
+        return getVoiceMessageCount(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the voice mail count for a subscription. Return 0 if unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * @param subId whose voice message count is returned
+     */
+    /** {@hide} */
+    public int getVoiceMessageCount(long subId) {
         try {
-            return getITelephony().getVoiceMessageCount();
+            return getITelephony().getVoiceMessageCountUsingSubId(subId);
         } catch (RemoteException ex) {
             return 0;
         } catch (NullPointerException ex) {
@@ -1373,8 +1874,22 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getVoiceMailAlphaTag() {
+        return getVoiceMailAlphaTag(getDefaultSubscription());
+    }
+
+    /**
+     * Retrieves the alphabetic identifier associated with the voice
+     * mail number for a subscription.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * @param subId whose alphabetic identifier associated with the
+     * voice mail number is returned
+     */
+    /** {@hide} */
+    public String getVoiceMailAlphaTag(long subId) {
         try {
-            return getSubscriberInfo().getVoiceMailAlphaTag();
+            return getSubscriberInfo().getVoiceMailAlphaTagUsingSubId(subId);
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1437,7 +1952,6 @@
         return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
     }
 
-
     /** Device call state: No activity. */
     public static final int CALL_STATE_IDLE = 0;
     /** Device call state: Ringing. A new call arrived and is
@@ -1453,8 +1967,19 @@
      * Returns a constant indicating the call state (cellular) on the device.
      */
     public int getCallState() {
+        return getCallState(getDefaultSubscription());
+    }
+
+    /**
+     * Returns a constant indicating the call state (cellular) on the device
+     * for a subscription.
+     *
+     * @param subId whose call state is returned
+     */
+    /** {@hide} */
+    public int getCallState(long subId) {
         try {
-            return getITelephony().getCallState();
+            return getITelephony().getCallStateUsingSubId(subId);
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return CALL_STATE_IDLE;
@@ -1575,8 +2100,8 @@
     public void listen(PhoneStateListener listener, int events) {
         String pkgForDebug = mContext != null ? mContext.getPackageName() : "<unknown>";
         try {
-            Boolean notifyNow = true;
-            sRegistry.listen(pkgForDebug, listener.callback, events, notifyNow);
+            Boolean notifyNow = (getITelephony() != null);
+            sRegistry.listenUsingSubId(listener.mSubId, pkgForDebug, listener.callback, events, notifyNow);
         } catch (RemoteException ex) {
             // system process dead
         } catch (NullPointerException ex) {
@@ -1590,8 +2115,16 @@
      * @hide
      */
     public int getCdmaEriIconIndex() {
+        return getCdmaEriIconIndex(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the CDMA ERI icon index to display for a subscription
+     */
+    /** {@hide} */
+    public int getCdmaEriIconIndex(long subId) {
         try {
-            return getITelephony().getCdmaEriIconIndex();
+            return getITelephony().getCdmaEriIconIndexUsingSubId(subId);
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return -1;
@@ -1608,8 +2141,18 @@
      * @hide
      */
     public int getCdmaEriIconMode() {
+        return getCdmaEriIconMode(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the CDMA ERI icon mode for a subscription.
+     * 0 - ON
+     * 1 - FLASHING
+     */
+    /** {@hide} */
+    public int getCdmaEriIconMode(long subId) {
         try {
-            return getITelephony().getCdmaEriIconMode();
+            return getITelephony().getCdmaEriIconModeUsingSubId(subId);
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return -1;
@@ -1624,8 +2167,17 @@
      * @hide
      */
     public String getCdmaEriText() {
+        return getCdmaEriText(getDefaultSubscription());
+    }
+
+    /**
+     * Returns the CDMA ERI text, of a subscription
+     *
+     */
+    /** {@hide} */
+    public String getCdmaEriText(long subId) {
         try {
-            return getITelephony().getCdmaEriText();
+            return getITelephony().getCdmaEriTextUsingSubId(subId);
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return null;
@@ -1743,6 +2295,504 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
+     * Returns Default subscription.
+     */
+    private static long getDefaultSubscription() {
+        return SubscriptionManager.getDefaultSubId();
+    }
+
+    /** {@hide} */
+    public int getDefaultSim() {
+        //TODO Need to get it from Telephony Devcontroller
+        return 0;
+    }
+
+    /**
+     * Sets the telephony property with the value specified.
+     *
+     * @hide
+     */
+    public static void setTelephonyProperty(String property, long subId, String value) {
+        String propVal = "";
+        String p[] = null;
+        String prop = SystemProperties.get(property);
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+
+        if (value == null) {
+            value = "";
+        }
+
+        if (prop != null) {
+            p = prop.split(",");
+        }
+
+        if (phoneId < 0) return;
+
+        for (int i = 0; i < phoneId; i++) {
+            String str = "";
+            if ((p != null) && (i < p.length)) {
+                str = p[i];
+            }
+            propVal = propVal + str + ",";
+        }
+
+        propVal = propVal + value;
+        if (p != null) {
+            for (int i = phoneId + 1; i < p.length; i++) {
+                propVal = propVal + "," + p[i];
+            }
+        }
+
+        // TODO: workaround for QC
+        if (property.length() > SystemProperties.PROP_NAME_MAX || propVal.length() > SystemProperties.PROP_VALUE_MAX) {
+            Rlog.d(TAG, "setTelephonyProperty length too long:" + property + ", " + propVal);
+            return;
+        }
+
+        Rlog.d(TAG, "setTelephonyProperty property=" + property + " propVal=" + propVal);
+        SystemProperties.set(property, propVal);
+    }
+
+    /**
+     * Convenience function for retrieving a value from the secure settings
+     * value list as an integer.  Note that internally setting values are
+     * always stored as strings; this function converts the string to an
+     * integer for you.
+     * <p>
+     * This version does not take a default value.  If the setting has not
+     * been set, or the string value is not a number,
+     * it throws {@link SettingNotFoundException}.
+     *
+     * @param cr The ContentResolver to access.
+     * @param name The name of the setting to retrieve.
+     * @param index The index of the list
+     *
+     * @throws SettingNotFoundException Thrown if a setting by the given
+     * name can't be found or the setting value is not an integer.
+     *
+     * @return The value at the given index of settings.
+     * @hide
+     */
+    public static int getIntAtIndex(android.content.ContentResolver cr,
+            String name, int index)
+            throws android.provider.Settings.SettingNotFoundException {
+        String v = android.provider.Settings.Global.getString(cr, name);
+        if (v != null) {
+            String valArray[] = v.split(",");
+            if ((index >= 0) && (index < valArray.length) && (valArray[index] != null)) {
+                try {
+                    return Integer.parseInt(valArray[index]);
+                } catch (NumberFormatException e) {
+                    //Log.e(TAG, "Exception while parsing Integer: ", e);
+                }
+            }
+        }
+        throw new android.provider.Settings.SettingNotFoundException(name);
+    }
+
+    /**
+     * Convenience function for updating settings value as coma separated
+     * integer values. This will either create a new entry in the table if the
+     * given name does not exist, or modify the value of the existing row
+     * with that name.  Note that internally setting values are always
+     * stored as strings, so this function converts the given value to a
+     * string before storing it.
+     *
+     * @param cr The ContentResolver to access.
+     * @param name The name of the setting to modify.
+     * @param index The index of the list
+     * @param value The new value for the setting to be added to the list.
+     * @return true if the value was set, false on database errors
+     * @hide
+     */
+    public static boolean putIntAtIndex(android.content.ContentResolver cr,
+            String name, int index, int value) {
+        String data = "";
+        String valArray[] = null;
+        String v = android.provider.Settings.Global.getString(cr, name);
+
+        if (v != null) {
+            valArray = v.split(",");
+        }
+
+        // Copy the elements from valArray till index
+        for (int i = 0; i < index; i++) {
+            String str = "";
+            if ((valArray != null) && (i < valArray.length)) {
+                str = valArray[i];
+            }
+            data = data + str + ",";
+        }
+
+        data = data + value;
+
+        // Copy the remaining elements from valArray if any.
+        if (valArray != null) {
+            for (int i = index+1; i < valArray.length; i++) {
+                data = data + "," + valArray[i];
+            }
+        }
+        return android.provider.Settings.Global.putString(cr, name, data);
+    }
+
+    /**
+     * Gets the telephony property.
+     *
+     * @hide
+     */
+    public static String getTelephonyProperty(String property, long subId, String defaultVal) {
+        String propVal = null;
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+        String prop = SystemProperties.get(property);
+        if ((prop != null) && (prop.length() > 0)) {
+            String values[] = prop.split(",");
+            if ((phoneId >= 0) && (phoneId < values.length) && (values[phoneId] != null)) {
+                propVal = values[phoneId];
+            }
+        }
+        return propVal == null ? defaultVal : propVal;
+    }
+
+    /** @hide */
+    public int getSimCount() {
+        if(isMultiSimEnabled()) {
+        //TODO Need to get it from Telephony Devcontroller
+            return 2;
+        } else {
+           return 1;
+        }
+    }
+
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     * @hide
+     */
+    public String getIsimIst() {
+        try {
+            return getSubscriberInfo().getIsimIst();
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of PCSCF strings with one PCSCF per string, or null if
+     *         not present or not loaded
+     * @hide
+     */
+    public String[] getIsimPcscf() {
+        try {
+            return getSubscriberInfo().getIsimPcscf();
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
+     * Returns the response of ISIM Authetification through RIL.
+     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
+     * @return the response of ISIM Authetification, or null if not available
+     * @hide
+     * @deprecated
+     * @see getIccSimChallengeResponse with appType=PhoneConstants.APPTYPE_ISIM
+     */
+    public String getIsimChallengeResponse(String nonce){
+        try {
+            return getSubscriberInfo().getIsimChallengeResponse(nonce);
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
+     * Returns the response of SIM Authentication through RIL.
+     * Returns null if the Authentication hasn't been successful
+     * @param subId subscription ID to be queried
+     * @param appType ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @param data authentication challenge data
+     * @return the response of SIM Authentication, or null if not available
+     * @hide
+     */
+    public String getIccSimChallengeResponse(long subId, int appType, String data) {
+        try {
+            return getSubscriberInfo().getIccSimChallengeResponse(subId, appType, data);
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone starts
+            return null;
+        }
+    }
+
+    /**
+     * Returns the response of SIM Authentication through RIL for the default subscription.
+     * Returns null if the Authentication hasn't been successful
+     * @param appType ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @param data authentication challenge data
+     * @return the response of SIM Authentication, or null if not available
+     * @hide
+     */
+    public String getIccSimChallengeResponse(int appType, String data) {
+        return getIccSimChallengeResponse(getDefaultSubscription(), appType, data);
+    }
+
+    /**
+     * Get P-CSCF address from PCO after data connection is established or modified.
+     *
+     * @return array of P-CSCF address
+     * @hide
+     */
+    public String[] getPcscfAddress() {
+        try {
+            return getITelephony().getPcscfAddress();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
+    /**
+     * Set IMS registration state
+     *
+     * @param Registration state
+     * @hide
+     */
+    public void setImsRegistrationState(boolean registered) {
+        try {
+            getITelephony().setImsRegistrationState(registered);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * 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
      */
 
@@ -1985,28 +3035,6 @@
 
     /** @hide */
     @SystemApi
-    public int enableApnType(String type) {
-        try {
-            return getITelephony().enableApnType(type);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#enableApnType", e);
-        }
-        return PhoneConstants.APN_REQUEST_FAILED;
-    }
-
-    /** @hide */
-    @SystemApi
-    public int disableApnType(String type) {
-        try {
-            return getITelephony().disableApnType(type);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#disableApnType", e);
-        }
-        return PhoneConstants.APN_REQUEST_FAILED;
-    }
-
-    /** @hide */
-    @SystemApi
     public boolean enableDataConnectivity() {
         try {
             return getITelephony().enableDataConnectivity();
@@ -2069,4 +3097,40 @@
         }
         return false;
     }
+
+    /**
+     * Return a list of Subscriptions that can be used to indicate a preference when making
+     * a phone call.
+     *
+     * @see #EXTRA_SUBSCRIPTION
+     * @return A list of {@code Subscription} objects.
+     */
+    public List<Subscription> getSubscriptions() {
+        try {
+            return getTelecommService().getSubscriptions();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getSubscriptions", e);
+        }
+        return null;
+    }
+
+    /** @hide */
+    @SystemApi
+    public void setEnabled(Subscription subscription, boolean enabled) {
+        try {
+            getTelecommService().setEnabled(subscription, enabled);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setEnabled", e);
+        }
+    }
+
+    /** @hide */
+    @SystemApi
+    public void setSystemDefault(Subscription subscription) {
+        try {
+            getTelecommService().setSystemDefault(subscription);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setSystemDefault", e);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/VoLteServiceState.aidl b/telephony/java/android/telephony/VoLteServiceState.aidl
new file mode 100644
index 0000000..59dd04f
--- /dev/null
+++ b/telephony/java/android/telephony/VoLteServiceState.aidl
@@ -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 android.telephony;
+
+parcelable VoLteServiceState;
+
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
new file mode 100644
index 0000000..afef601b
--- /dev/null
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -0,0 +1,229 @@
+/*
+ * 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.telephony;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Rlog;
+
+/**
+ * Contains LTE network state related information.
+ *
+ * @hide
+ */
+public final class VoLteServiceState implements Parcelable {
+
+    private static final String LOG_TAG = "VoLteServiceState";
+    private static final boolean DBG = false;
+
+    //Use int max, as -1 is a valid value in signal strength
+    public static final int INVALID = 0x7FFFFFFF;
+
+    public static final int NOT_SUPPORTED = 0;
+    public static final int SUPPORTED = 1;
+
+    // Single Radio Voice Call Continuity(SRVCC) progress state
+    public static final int HANDOVER_STARTED   = 0;
+    public static final int HANDOVER_COMPLETED = 1;
+    public static final int HANDOVER_FAILED    = 2;
+    public static final int HANDOVER_CANCELED  = 3;
+
+    private int mSrvccState;
+
+    /**
+     * Create a new VoLteServiceState from a intent notifier Bundle
+     *
+     * This method is used by PhoneStateIntentReceiver and maybe by
+     * external applications.
+     *
+     * @param m Bundle from intent notifier
+     * @return newly created VoLteServiceState
+     *
+     * @hide
+     */
+    public static VoLteServiceState newFromBundle(Bundle m) {
+        VoLteServiceState ret;
+        ret = new VoLteServiceState();
+        ret.setFromNotifierBundle(m);
+        return ret;
+    }
+
+    /**
+     * Empty constructor
+     *
+     * @hide
+     */
+    public VoLteServiceState() {
+        initialize();
+    }
+
+    /**
+     * Constructor
+     *
+     * @hide
+     */
+    public VoLteServiceState(int srvccState) {
+        initialize();
+
+        mSrvccState = srvccState;
+    }
+
+    /**
+     * Copy constructors
+     *
+     * @param s Source VoLteServiceState
+     *
+     * @hide
+     */
+    public VoLteServiceState(VoLteServiceState s) {
+        copyFrom(s);
+    }
+
+    /**
+     * Initialize values to defaults.
+     *
+     * @hide
+     */
+    private void initialize() {
+        mSrvccState = INVALID;
+    }
+
+    /**
+     * @hide
+     */
+    protected void copyFrom(VoLteServiceState s) {
+        mSrvccState = s.mSrvccState;
+    }
+
+    /**
+     * Construct a VoLteServiceState object from the given parcel.
+     *
+     * @hide
+     */
+    public VoLteServiceState(Parcel in) {
+        if (DBG) log("Size of VoLteServiceState parcel:" + in.dataSize());
+
+        mSrvccState = in.readInt();
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSrvccState);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable.Creator}
+     *
+     * @hide
+     */
+    public static final Parcelable.Creator<VoLteServiceState> CREATOR = new Parcelable.Creator() {
+        public VoLteServiceState createFromParcel(Parcel in) {
+            return new VoLteServiceState(in);
+        }
+
+        public VoLteServiceState[] newArray(int size) {
+            return new VoLteServiceState[size];
+        }
+    };
+
+    /**
+     * Validate the individual fields as per the range
+     * specified in ril.h
+     * Set to invalid any field that is not in the valid range
+     *
+     * @return
+     *      Valid values for all fields
+     * @hide
+     */
+    public void validateInput() {
+    }
+
+    public int hashCode() {
+        int primeNum = 31;
+        return ((mSrvccState * primeNum));
+    }
+
+    /**
+     * @return true if the LTE network states are the same
+     */
+    @Override
+    public boolean equals (Object o) {
+        VoLteServiceState s;
+
+        try {
+            s = (VoLteServiceState) o;
+        } catch (ClassCastException ex) {
+            return false;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        return (mSrvccState == s.mSrvccState);
+    }
+
+    /**
+     * @return string representation.
+     */
+    @Override
+    public String toString() {
+        return ("VoLteServiceState:"
+                + " " + mSrvccState);
+    }
+
+    /**
+     * Set VoLteServiceState based on intent notifier map
+     *
+     * @param m intent notifier map
+     * @hide
+     */
+    private void setFromNotifierBundle(Bundle m) {
+        mSrvccState = m.getInt("mSrvccState");
+    }
+
+    /**
+     * Set intent notifier Bundle based on VoLteServiceState
+     *
+     * @param m intent notifier Bundle
+     * @hide
+     */
+    public void fillInNotifierBundle(Bundle m) {
+        m.putInt("mSrvccState", mSrvccState);
+    }
+
+    public int getSrvccState() {
+        return mSrvccState;
+    }
+
+    /**
+     * log
+     */
+    private static void log(String s) {
+        Rlog.w(LOG_TAG, s);
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl b/telephony/java/com/android/ims/ImsCallForwardInfo.aidl
new file mode 100644
index 0000000..a7c3f9a
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallForwardInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsCallForwardInfo;
diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.java b/telephony/java/com/android/ims/ImsCallForwardInfo.java
new file mode 100644
index 0000000..3f8fd19
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallForwardInfo.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the call forward information for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsCallForwardInfo implements Parcelable {
+    // Refer to ImsUtInterface#CDIV_CF_XXX
+    public int mCondition;
+    // 0: disabled, 1: enabled
+    public int mStatus;
+    // 0x91: International, 0x81: Unknown
+    public int mToA;
+    // Number (it will not include the "sip" or "tel" URI scheme)
+    public String mNumber;
+    // No reply timer for CF
+    public int mTimeSeconds;
+
+    public ImsCallForwardInfo() {
+    }
+
+    public ImsCallForwardInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCondition);
+        out.writeInt(mStatus);
+        out.writeInt(mToA);
+        out.writeString(mNumber);
+        out.writeInt(mTimeSeconds);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", Condition: " + mCondition
+            + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
+            + ", ToA: " + mToA + ", Number=" + mNumber
+            + ", Time (seconds): " + mTimeSeconds;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mCondition = in.readInt();
+        mStatus = in.readInt();
+        mToA = in.readInt();
+        mNumber = in.readString();
+        mTimeSeconds = in.readInt();
+    }
+
+    public static final Creator<ImsCallForwardInfo> CREATOR =
+            new Creator<ImsCallForwardInfo>() {
+        @Override
+        public ImsCallForwardInfo createFromParcel(Parcel in) {
+            return new ImsCallForwardInfo(in);
+        }
+
+        @Override
+        public ImsCallForwardInfo[] newArray(int size) {
+            return new ImsCallForwardInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/ImsCallProfile.aidl b/telephony/java/com/android/ims/ImsCallProfile.aidl
new file mode 100644
index 0000000..a356d13
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsCallProfile;
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
new file mode 100644
index 0000000..208f467
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -0,0 +1,289 @@
+/*
+ * 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.ims;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS call profile.
+ * It is created from GSMA IR.92/IR.94, 3GPP TS 24.229/TS 26.114/TS26.111.
+ * It provides the service and call type, the additional information related to the call.
+ *
+ * @hide
+ */
+public class ImsCallProfile implements Parcelable {
+    private static final String TAG = "ImsCallProfile";
+
+    /**
+     * Service types
+     */
+    /**
+     * It is for a special case. It helps that the application can make a call
+     * without IMS connection (not registered).
+     * In the moment of the call initiation, the device try to connect to the IMS network
+     * and initiates the call.
+     */
+    public static final int SERVICE_TYPE_NONE = 0;
+    /**
+     * It is a default type and can be selected when the device is connected to the IMS network.
+     */
+    public static final int SERVICE_TYPE_NORMAL = 1;
+    /**
+     * It is for an emergency call.
+     */
+    public static final int SERVICE_TYPE_EMERGENCY = 2;
+
+    /**
+     * Call types
+     */
+    /**
+     * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+     */
+    public static final int CALL_TYPE_VOICE_N_VIDEO = 1;
+    /**
+     * IR.92 (Voice only)
+     */
+    public static final int CALL_TYPE_VOICE = 2;
+    /**
+     * VT to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+     */
+    public static final int CALL_TYPE_VIDEO_N_VOICE = 3;
+    /**
+     * Video Telephony (audio / video two way)
+     */
+    public static final int CALL_TYPE_VT = 4;
+    /**
+     * Video Telephony (audio two way / video TX one way)
+     */
+    public static final int CALL_TYPE_VT_TX = 5;
+    /**
+     * Video Telephony (audio two way / video RX one way)
+     */
+    public static final int CALL_TYPE_VT_RX = 6;
+    /**
+     * Video Telephony (audio two way / video inactive)
+     */
+    public static final int CALL_TYPE_VT_NODIR = 7;
+    /**
+     * VideoShare (video two way)
+     */
+    public static final int CALL_TYPE_VS = 8;
+    /**
+     * VideoShare (video TX one way)
+     */
+    public static final int CALL_TYPE_VS_TX = 9;
+    /**
+     * VideoShare (video RX one way)
+     */
+    public static final int CALL_TYPE_VS_RX = 10;
+
+    /**
+     * Extra properties for IMS call.
+     */
+    /**
+     * Boolean extra properties - "true" / "false"
+     *  conference : Indicates if the session is for the conference call or not.
+     *  e_call : Indicates if the session is for the emergency call or not.
+     *  vms : Indicates if the session is connected to the voice mail system or not.
+     *  call_mode_changeable : Indicates if the session is able to upgrade/downgrade
+     *      the video during voice call.
+     *  conference_avail : Indicates if the session can be extended to the conference.
+     */
+    public static final String EXTRA_CONFERENCE = "conference";
+    public static final String EXTRA_E_CALL = "e_call";
+    public static final String EXTRA_VMS = "vms";
+    public static final String EXTRA_CALL_MODE_CHANGEABLE = "call_mode_changeable";
+    public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail";
+
+    /**
+     * Integer extra properties
+     *  oir : Rule for originating identity (number) presentation, MO/MT.
+     *      {@link ImsCallProfile#OIR_DEFAULT}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+     *  cnap : Rule for calling name presentation
+     *      {@link ImsCallProfile#OIR_DEFAULT}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+     *      {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+     *  dialstring : To identify the Ims call type, MO
+     *      {@link ImsCallProfile#DIALSTRING_NORMAL_CALL}
+     *      {@link ImsCallProfile#DIALSTRING_SS_CONF}
+     *      {@link ImsCallProfile#DIALSTRING_USSD}
+     */
+    public static final String EXTRA_OIR = "oir";
+    public static final String EXTRA_CNAP = "cnap";
+    public static final String EXTRA_DIALSTRING = "dialstring";
+
+    /**
+     * Values for EXTRA_OIR / EXTRA_CNAP
+     */
+    public static final int OIR_DEFAULT = 0;    // "user subscription default value"
+    public static final int OIR_PRESENTATION_RESTRICTED = 1;
+    public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2;
+
+    /**
+     * Values for EXTRA_DIALSTRING
+     */
+    // default (normal call)
+    public static final int DIALSTRING_NORMAL = 0;
+    // Call for SIP-based user configuration
+    public static final int DIALSTRING_SS_CONF = 1;
+    // Call for USSD message
+    public static final int DIALSTRING_USSD = 2;
+
+    /**
+     * String extra properties
+     *  oi : Originating identity (number), MT only
+     *  cna : Calling name
+     *  ussd : For network-initiated USSD, MT only
+     *  remote_uri : Connected user identity (it can be used for the conference)
+     */
+    public static final String EXTRA_OI = "oi";
+    public static final String EXTRA_CNA = "cna";
+    public static final String EXTRA_USSD = "ussd";
+    public static final String EXTRA_REMOTE_URI = "remote_uri";
+
+    public int mServiceType;
+    public int mCallType;
+    public Bundle mCallExtras;
+    public ImsStreamMediaProfile mMediaProfile;
+
+
+
+    public ImsCallProfile(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsCallProfile() {
+        mServiceType = SERVICE_TYPE_NORMAL;
+        mCallType = CALL_TYPE_VOICE_N_VIDEO;
+        mCallExtras = new Bundle();
+        mMediaProfile = new ImsStreamMediaProfile();
+    }
+
+    public ImsCallProfile(int serviceType, int callType) {
+        mServiceType = serviceType;
+        mCallType = callType;
+        mCallExtras = new Bundle();
+        mMediaProfile = new ImsStreamMediaProfile();
+    }
+
+    public String getCallExtra(String name) {
+        return getCallExtra(name, "");
+    }
+
+    public String getCallExtra(String name, String defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getString(name, defaultValue);
+    }
+
+    public boolean getCallExtraBoolean(String name) {
+        return getCallExtraBoolean(name, false);
+    }
+
+    public boolean getCallExtraBoolean(String name, boolean defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getBoolean(name, defaultValue);
+    }
+
+    public int getCallExtraInt(String name) {
+        return getCallExtraInt(name, -1);
+    }
+
+    public int getCallExtraInt(String name, int defaultValue) {
+        if (mCallExtras == null) {
+            return defaultValue;
+        }
+
+        return mCallExtras.getInt(name, defaultValue);
+    }
+
+    public void setCallExtra(String name, String value) {
+        if (mCallExtras != null) {
+            mCallExtras.putString(name, value);
+        }
+    }
+
+    public void setCallExtraBoolean(String name, boolean value) {
+        if (mCallExtras != null) {
+            mCallExtras.putBoolean(name, value);
+        }
+    }
+
+    public void setCallExtraInt(String name, int value) {
+        if (mCallExtras != null) {
+            mCallExtras.putInt(name, value);
+        }
+    }
+
+    public void updateCallType(ImsCallProfile profile) {
+        mCallType = profile.mCallType;
+    }
+
+    public void updateCallExtras(ImsCallProfile profile) {
+        mCallExtras.clear();
+        mCallExtras = (Bundle) profile.mCallExtras.clone();
+    }
+
+    @Override
+    public String toString() {
+        return "{ serviceType=" + mServiceType +
+                ", callType=" + mCallType +
+                ", callExtras=" + mCallExtras.toString() +
+                ", mediaProfile=" + mMediaProfile.toString() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mServiceType);
+        out.writeInt(mCallType);
+        out.writeParcelable(mCallExtras, 0);
+        out.writeParcelable(mMediaProfile, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mServiceType = in.readInt();
+        mCallType = in.readInt();
+        mCallExtras = in.readParcelable(null);
+        mMediaProfile = in.readParcelable(null);
+    }
+
+    public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
+        @Override
+        public ImsCallProfile createFromParcel(Parcel in) {
+            return new ImsCallProfile(in);
+        }
+
+        @Override
+        public ImsCallProfile[] newArray(int size) {
+            return new ImsCallProfile[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/ImsConferenceState.aidl b/telephony/java/com/android/ims/ImsConferenceState.aidl
new file mode 100644
index 0000000..2fc029f
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConferenceState.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsConferenceState;
diff --git a/telephony/java/com/android/ims/ImsConferenceState.java b/telephony/java/com/android/ims/ImsConferenceState.java
new file mode 100644
index 0000000..f708d5b
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConferenceState.java
@@ -0,0 +1,142 @@
+/*
+ * 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.ims;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the conference information (defined in RFC 4575) for IMS conference call.
+ *
+ * @hide
+ */
+public class ImsConferenceState implements Parcelable {
+    /**
+     * conference-info : user
+     */
+    // user (String) : Tel or SIP URI
+    public static final String USER = "user";
+    // user > display text (String)
+    public static final String DISPLAY_TEXT = "display-text";
+    // user > endpoint (String) : URI or GRUU or Phone number
+    public static final String ENDPOINT = "endpoint";
+    // user > endpoint > status
+    public static final String STATUS = "status";
+
+    /**
+     * status-type (String) :
+     * "pending" : Endpoint is not yet in the session, but it is anticipated that he/she will
+     *      join in the near future.
+     * "dialing-out" : Focus has dialed out to connect the endpoint to the conference,
+     *      but the endpoint is not yet in the roster (probably being authenticated).
+     * "dialing-in" : Endpoint is dialing into the conference, not yet in the roster
+     *      (probably being authenticated).
+     * "alerting" : PSTN alerting or SIP 180 Ringing was returned for the outbound call;
+     *      endpoint is being alerted.
+     * "on-hold" : Active signaling dialog exists between an endpoint and a focus,
+     *      but endpoint is "on-hold" for this conference, i.e., he/she is neither "hearing"
+     *      the conference mix nor is his/her media being mixed in the conference.
+     * "connected" : Endpoint is a participant in the conference. Depending on the media policies,
+     *      he/she can send and receive media to and from other participants.
+     * "disconnecting" : Focus is in the process of disconnecting the endpoint
+     *      (e.g. in SIP a DISCONNECT or BYE was sent to the endpoint).
+     * "disconnected" : Endpoint is not a participant in the conference, and no active dialog
+     *      exists between the endpoint and the focus.
+     * "muted-via-focus" : Active signaling dialog exists beween an endpoint and a focus and
+     *      the endpoint can "listen" to the conference, but the endpoint's media is not being
+     *      mixed into the conference.
+     * "connect-fail" : Endpoint fails to join the conference by rejecting the conference call.
+     */
+    public static final String STATUS_PENDING = "pending";
+    public static final String STATUS_DIALING_OUT = "dialing-out";
+    public static final String STATUS_DIALING_IN = "dialing-in";
+    public static final String STATUS_ALERTING = "alerting";
+    public static final String STATUS_ON_HOLD = "on-hold";
+    public static final String STATUS_CONNECTED = "connected";
+    public static final String STATUS_DISCONNECTING = "disconnecting";
+    public static final String STATUS_DISCONNECTED = "disconnected";
+    public static final String STATUS_MUTED_VIA_FOCUS = "muted-via-focus";
+    public static final String STATUS_CONNECT_FAIL = "connect-fail";
+
+    /**
+     * conference-info : SIP status code (integer)
+     */
+    public static final String SIP_STATUS_CODE = "sipstatuscode";
+
+    public HashMap<String, Bundle> mParticipants = new HashMap<String, Bundle>();
+
+    public ImsConferenceState() {
+    }
+
+    public ImsConferenceState(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mParticipants.size());
+
+        if (mParticipants.size() > 0) {
+            Set<Entry<String, Bundle>> entries = mParticipants.entrySet();
+
+            if (entries != null) {
+                Iterator<Entry<String, Bundle>> iterator = entries.iterator();
+
+                while (iterator.hasNext()) {
+                    Entry<String, Bundle> entry = iterator.next();
+
+                    out.writeString(entry.getKey());
+                    out.writeParcelable(entry.getValue(), 0);
+                }
+            }
+        }
+    }
+
+    private void readFromParcel(Parcel in) {
+        int size = in.readInt();
+
+        for (int i = 0; i < size; ++i) {
+            String user = in.readString();
+            Bundle state = in.readParcelable(null);
+            mParticipants.put(user, state);
+        }
+    }
+
+    public static final Creator<ImsConferenceState> CREATOR =
+            new Creator<ImsConferenceState>() {
+        @Override
+        public ImsConferenceState createFromParcel(Parcel in) {
+            return new ImsConferenceState(in);
+        }
+
+        @Override
+        public ImsConferenceState[] newArray(int size) {
+            return new ImsConferenceState[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.aidl b/telephony/java/com/android/ims/ImsReasonInfo.aidl
new file mode 100644
index 0000000..17e6d3a
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsReasonInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsReasonInfo;
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
new file mode 100644
index 0000000..99faba6
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -0,0 +1,326 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class enables an application to get details on why a method call failed.
+ *
+ * @hide
+ */
+public class ImsReasonInfo implements Parcelable {
+
+    /**
+     * Reason types, defines the error category.
+     *    UNSPECIFIED - unknown error reason
+     *    LOCAL - indicates the local/device error reason
+     *    LOCAL_TIMEOUT - indicates the local error reason when a specific timer is expired
+     *    STATUSCODE - indicates the interworking error reason by SIP status code received
+     *        from the network
+     *    MEDIA - indicates the media error reason (local resource, SDP parameter, etc.)
+     *    USER - indicates the error reason by the local or remote user
+     *    UT - indicates the error reason for the supplementary service configuration
+     */
+    public static final int TYPE_UNSPECIFIED = 0;
+    public static final int TYPE_LOCAL = 1;
+    public static final int TYPE_TIMEOUT = 2;
+    public static final int TYPE_STATUSCODE = 3;
+    public static final int TYPE_MEDIA = 4;
+    public static final int TYPE_USER = 5;
+    public static final int TYPE_UT = 8;
+
+    /**
+     * Specific code of each types
+     */
+    public static final int CODE_UNSPECIFIED = 0;
+
+    /**
+     * LOCAL
+     */
+    // IMS -> Telephony
+    // The passed argument is an invalid
+    public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101;
+    // The operation is invoked in invalid call state
+    public static final int CODE_LOCAL_ILLEGAL_STATE = 102;
+    // IMS service internal error
+    public static final int CODE_LOCAL_INTERNAL_ERROR = 103;
+    // IMS service goes down (service connection is lost)
+    public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106;
+    // No pending incoming call exists
+    public static final int CODE_LOCAL_NO_PENDING_CALL = 107;
+
+    // IMS -> Telephony
+    // Service unavailable; by power off
+    public static final int CODE_LOCAL_POWER_OFF = 111;
+    // Service unavailable; by low battery
+    public static final int CODE_LOCAL_LOW_BATTERY = 112;
+    // Service unavailable; by out of service (data service state)
+    public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121;
+    // Service unavailable; by no LTE coverage
+    // (VoLTE is not supported even though IMS is registered)
+    public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122;
+    // Service unavailable; by located in roaming area
+    public static final int CODE_LOCAL_NETWORK_ROAMING = 123;
+    // Service unavailable; by IP changed
+    public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124;
+    // Service unavailable; other
+    public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131;
+    // Service unavailable; IMS connection is lost (IMS is not registered)
+    public static final int CODE_LOCAL_NOT_REGISTERED = 132;
+
+    // IMS <-> Telephony
+    // Max call exceeded
+    public static final int CODE_LOCAL_CALL_EXCEEDED = 141;
+    // IMS <- Telephony
+    // Call busy
+    public static final int CODE_LOCAL_CALL_BUSY = 142;
+    // Call decline
+    public static final int CODE_LOCAL_CALL_DECLINE = 143;
+    // IMS -> Telephony
+    // SRVCC is in progress
+    public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144;
+    // Resource reservation is failed (QoS precondition)
+    public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145;
+    // Retry CS call; VoLTE service can't be provided by the network or remote end
+    // Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+    public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146;
+    // Retry VoLTE call; VoLTE service can't be provided by the network temporarily
+    public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147;
+    // IMS call is already terminated (in TERMINATED state)
+    public static final int CODE_LOCAL_CALL_TERMINATED = 148;
+
+    /**
+     * TIMEOUT (IMS -> Telephony)
+     */
+    // 1xx waiting timer is expired after sending INVITE request (MO only)
+    public static final int CODE_TIMEOUT_1XX_WAITING = 201;
+    // User no answer during call setup operation (MO/MT)
+    // MO : 200 OK to INVITE request is not received,
+    // MT : No action from user after alerting the call
+    public static final int CODE_TIMEOUT_NO_ANSWER = 202;
+    // User no answer during call update operation (MO/MT)
+    // MO : 200 OK to re-INVITE request is not received,
+    // MT : No action from user after alerting the call
+    public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
+
+    /**
+     * STATUSCODE (SIP response code) (IMS -> Telephony)
+     */
+    // 3xx responses
+    // SIP request is redirected
+    public static final int CODE_SIP_REDIRECTED = 321;
+    // 4xx responses
+    // 400 : Bad Request
+    public static final int CODE_SIP_BAD_REQUEST = 331;
+    // 403 : Forbidden
+    public static final int CODE_SIP_FORBIDDEN = 332;
+    // 404 : Not Found
+    public static final int CODE_SIP_NOT_FOUND = 333;
+    // 415 : Unsupported Media Type
+    // 416 : Unsupported URI Scheme
+    // 420 : Bad Extension
+    public static final int CODE_SIP_NOT_SUPPORTED = 334;
+    // 408 : Request Timeout
+    public static final int CODE_SIP_REQUEST_TIMEOUT = 335;
+    // 480 : Temporarily Unavailable
+    public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336;
+    // 484 : Address Incomplete
+    public static final int CODE_SIP_BAD_ADDRESS = 337;
+    // 486 : Busy Here
+    // 600 : Busy Everywhere
+    public static final int CODE_SIP_BUSY = 338;
+    // 487 : Request Terminated
+    public static final int CODE_SIP_REQUEST_CANCELLED = 339;
+    // 406 : Not Acceptable
+    // 488 : Not Acceptable Here
+    // 606 : Not Acceptable
+    public static final int CODE_SIP_NOT_ACCEPTABLE = 340;
+    // 410 : Gone
+    // 604 : Does Not Exist Anywhere
+    public static final int CODE_SIP_NOT_REACHABLE = 341;
+    // Others
+    public static final int CODE_SIP_CLIENT_ERROR = 342;
+    // 5xx responses
+    // 501 : Server Internal Error
+    public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351;
+    // 503 : Service Unavailable
+    public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352;
+    // 504 : Server Time-out
+    public static final int CODE_SIP_SERVER_TIMEOUT = 353;
+    // Others
+    public static final int CODE_SIP_SERVER_ERROR = 354;
+    // 6xx responses
+    // 603 : Decline
+    public static final int CODE_SIP_USER_REJECTED = 361;
+    // Others
+    public static final int CODE_SIP_GLOBAL_ERROR = 362;
+
+    /**
+     * MEDIA (IMS -> Telephony)
+     */
+    // Media resource initialization failed
+    public static final int CODE_MEDIA_INIT_FAILED = 401;
+    // RTP timeout (no audio / video traffic in the session)
+    public static final int CODE_MEDIA_NO_DATA = 402;
+    // Media is not supported; so dropped the call
+    public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403;
+    // Unknown media related errors
+    public static final int CODE_MEDIA_UNSPECIFIED = 404;
+
+    /**
+     * USER
+     */
+    // Telephony -> IMS
+    // User triggers the call end
+    public static final int CODE_USER_TERMINATED = 501;
+    // No action while an incoming call is ringing
+    public static final int CODE_USER_NOANSWER = 502;
+    // User ignores an incoming call
+    public static final int CODE_USER_IGNORE = 503;
+    // User declines an incoming call
+    public static final int CODE_USER_DECLINE = 504;
+    // IMS -> Telephony
+    // The call is terminated by the network or remote user
+    public static final int CODE_USER_TERMINATED_BY_REMOTE = 510;
+
+    /**
+     * Extra codes for the specific code value
+     * This value can be referred when the code is CODE_LOCAL_CALL_CS_RETRY_REQUIRED.
+     */
+    // Try to connect CS call; normal
+    public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1;
+    // Try to connect CS call without the notification to user
+    public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2;
+    // Try to connect CS call by the settings of the menu
+    public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3;
+
+    /**
+     * UT
+     */
+    public static final int CODE_UT_NOT_SUPPORTED = 801;
+    public static final int CODE_UT_SERVICE_UNAVAILABLE = 802;
+    public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803;
+    public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821;
+
+
+
+    // For reason type
+    public int mReasonType;
+    // For main reason code
+    public int mCode;
+    // For the extra code value; it depends on the code value.
+    public int mExtraCode;
+    // For the additional message of the reason info.
+    public String mExtraMessage;
+
+    public ImsReasonInfo() {
+        mReasonType = TYPE_UNSPECIFIED;
+        mCode = CODE_UNSPECIFIED;
+        mExtraCode = CODE_UNSPECIFIED;
+        mExtraMessage = null;
+    }
+
+    public ImsReasonInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsReasonInfo(int code, int extraCode) {
+        mReasonType = (int) (code / 100);
+        mCode = code;
+        mExtraCode = extraCode;
+        mExtraMessage = null;
+    }
+
+    public ImsReasonInfo(int code, int extraCode, String extraMessage) {
+        mReasonType = (int) (code / 100);
+        mCode = code;
+        mExtraCode = extraCode;
+        mExtraMessage = extraMessage;
+    }
+
+    /**
+     *
+     */
+    public int getCode() {
+        return mCode;
+    }
+
+    /**
+     *
+     */
+    public int getExtraCode() {
+        return mExtraCode;
+    }
+
+    /**
+     *
+     */
+    public String getExtraMessage() {
+        return mExtraMessage;
+    }
+
+    /**
+     *
+     */
+    public int getReasonType() {
+        return mReasonType;
+    }
+
+    /**
+     * Returns the string format of {@link ImsReasonInfo}
+     *
+     * @return the string format of {@link ImsReasonInfo}
+     */
+    public String toString() {
+        return "ImsReasonInfo :: {" + mReasonType + ", "
+                + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mReasonType);
+        out.writeInt(mCode);
+        out.writeInt(mExtraCode);
+        out.writeString(mExtraMessage);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mReasonType = in.readInt();
+        mCode = in.readInt();
+        mExtraCode = in.readInt();
+        mExtraMessage = in.readString();
+    }
+
+    public static final Creator<ImsReasonInfo> CREATOR = new Creator<ImsReasonInfo>() {
+        @Override
+        public ImsReasonInfo createFromParcel(Parcel in) {
+            return new ImsReasonInfo(in);
+        }
+
+        @Override
+        public ImsReasonInfo[] newArray(int size) {
+            return new ImsReasonInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/ImsSsInfo.aidl b/telephony/java/com/android/ims/ImsSsInfo.aidl
new file mode 100644
index 0000000..0ac598b
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsSsInfo;
diff --git a/telephony/java/com/android/ims/ImsSsInfo.java b/telephony/java/com/android/ims/ImsSsInfo.java
new file mode 100644
index 0000000..dbde1c6
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsInfo.java
@@ -0,0 +1,76 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the result to the update operation for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsSsInfo implements Parcelable {
+    /**
+     * For the status of service registration or activation/deactivation.
+     */
+    public static final int NOT_REGISTERED = (-1);
+    public static final int DISABLED = 0;
+    public static final int ENABLED = 1;
+
+    // 0: disabled, 1: enabled
+    public int mStatus;
+
+    public ImsSsInfo() {
+    }
+
+    public ImsSsInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mStatus);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled");
+    }
+
+    private void readFromParcel(Parcel in) {
+        mStatus = in.readInt();
+    }
+
+    public static final Creator<ImsSsInfo> CREATOR =
+            new Creator<ImsSsInfo>() {
+        @Override
+        public ImsSsInfo createFromParcel(Parcel in) {
+            return new ImsSsInfo(in);
+        }
+
+        @Override
+        public ImsSsInfo[] newArray(int size) {
+            return new ImsSsInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
new file mode 100644
index 0000000..d648a35
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsStreamMediaProfile;
diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.java b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
new file mode 100644
index 0000000..003499c
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS stream media profile.
+ * It provides the media direction, quality of audio and/or video.
+ *
+ * @hide
+ */
+public class ImsStreamMediaProfile implements Parcelable {
+    private static final String TAG = "ImsStreamMediaProfile";
+
+    /**
+     * Media directions
+     */
+    public static final int DIRECTION_INVALID = (-1);
+    public static final int DIRECTION_INACTIVE = 0;
+    public static final int DIRECTION_RECEIVE = 1;
+    public static final int DIRECTION_SEND = 2;
+    public static final int DIRECTION_SEND_RECEIVE = 3;
+
+    /**
+     * Audio information
+     */
+    public static final int AUDIO_QUALITY_NONE = 0;
+    public static final int AUDIO_QUALITY_AMR = (1 << 0);
+    public static final int AUDIO_QUALITY_AMR_WB = (1 << 1);
+
+    /**
+     * Video information
+     */
+    public static final int VIDEO_QUALITY_NONE = 0;
+    public static final int VIDEO_QUALITY_QCIF = (1 << 0);
+    public static final int VIDEO_QUALITY_QVGA_LANDSCAPE = (1 << 1);
+    public static final int VIDEO_QUALITY_QVGA_PORTRAIT = (1 << 2);
+    public static final int VIDEO_QUALITY_VGA_LANDSCAPE = (1 << 3);
+    public static final int VIDEO_QUALITY_VGA_PORTRAIT = (1 << 4);
+
+    // Audio related information
+    public int mAudioQuality;
+    public int mAudioDirection;
+    // Video related information
+    public int mVideoQuality;
+    public int mVideoDirection;
+
+
+
+    public ImsStreamMediaProfile(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ImsStreamMediaProfile() {
+        mAudioQuality = AUDIO_QUALITY_AMR_WB;
+        mAudioDirection = DIRECTION_SEND_RECEIVE;
+        mVideoQuality = VIDEO_QUALITY_NONE;
+        mVideoDirection = DIRECTION_INVALID;
+    }
+
+    public ImsStreamMediaProfile(int audioQuality, int audioDirection,
+            int videoQuality, int videoDirection) {
+        mAudioQuality = audioQuality;
+        mAudioDirection = audioDirection;
+        mVideoQuality = videoQuality;
+        mVideoDirection = videoDirection;
+    }
+
+    public void copyFrom(ImsStreamMediaProfile profile) {
+        mAudioQuality = profile.mAudioQuality;
+        mAudioDirection = profile.mAudioDirection;
+        mVideoQuality = profile.mVideoQuality;
+        mVideoDirection = profile.mVideoDirection;
+    }
+
+    @Override
+    public String toString() {
+        return "{ audioQuality=" + mAudioQuality +
+                ", audioDirection=" + mAudioDirection +
+                ", videoQuality=" + mVideoQuality +
+                ", videoDirection=" + mVideoDirection + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mAudioQuality);
+        out.writeInt(mAudioDirection);
+        out.writeInt(mVideoQuality);
+        out.writeInt(mVideoDirection);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mAudioQuality = in.readInt();
+        mAudioDirection = in.readInt();
+        mVideoQuality = in.readInt();
+        mVideoDirection = in.readInt();
+    }
+
+    public static final Creator<ImsStreamMediaProfile> CREATOR =
+            new Creator<ImsStreamMediaProfile>() {
+        @Override
+        public ImsStreamMediaProfile createFromParcel(Parcel in) {
+            return new ImsStreamMediaProfile(in);
+        }
+
+        @Override
+        public ImsStreamMediaProfile[] newArray(int size) {
+            return new ImsStreamMediaProfile[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
new file mode 100644
index 0000000..5f2ff36
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -0,0 +1,219 @@
+/*
+ * 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.ims.internal;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.internal.IImsCallSessionListener;
+
+/**
+ * An IMS session that is associated with a SIP dialog which is established from/to
+ * INVITE request or a mid-call transaction to control the session.
+ * {@hide}
+ */
+interface IImsCallSession {
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    void close();
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    String getCallId();
+
+    /**
+     * Gets the call profile that this session is associated with
+     *
+     * @return the call profile that this session is associated with
+     */
+    ImsCallProfile getCallProfile();
+
+    /**
+     * Gets the local call profile that this session is associated with
+     *
+     * @return the local call profile that this session is associated with
+     */
+    ImsCallProfile getLocalCallProfile();
+
+    /**
+     * Gets the value associated with the specified property of this session.
+     *
+     * @return the string value associated with the specified property
+     */
+    String getProperty(String name);
+
+    /**
+     * Gets the session state. The value returned must be one of the states in
+     * {@link ImsCallSession#State}.
+     *
+     * @return the session state
+     */
+    int getState();
+
+    /**
+     * Checks if the session is in a call.
+     *
+     * @return true if the session is in a call
+     */
+    boolean isInCall();
+
+    /**
+     * Sets the listener to listen to the session events. A {@link IImsCallSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    void setListener(in IImsCallSessionListener listener);
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    void setMute(boolean muted);
+
+    /**
+     * Initiates an IMS call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param callee dialed string to make the call to
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    void start(String callee, in ImsCallProfile profile);
+
+    /**
+     * Initiates an IMS call with the specified participants and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession#State#IDLE}.
+     *
+     * @param participants participant list to initiate an IMS conference call
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    void startConference(in String[] participants, in ImsCallProfile profile);
+
+    /**
+     * Accepts an incoming call or session update.
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be answered
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+     * @see Listener#callSessionStarted
+     */
+    void accept(int callType, in ImsStreamMediaProfile profile);
+
+    /**
+     * Rejects an incoming call or session update.
+     *
+     * @param reason reason code to reject an incoming call
+     * @see Listener#callSessionStartFailed
+     */
+    void reject(int reason);
+
+    /**
+     * Terminates a call.
+     *
+     * @see Listener#callSessionTerminated
+     */
+    void terminate(int reason);
+
+    /**
+     * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+     * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
+     */
+    void hold(in ImsStreamMediaProfile profile);
+
+    /**
+     * Continues a call that's on hold. When it succeeds, {@link Listener#callSessionResumed}
+     * is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
+     * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
+     */
+    void resume(in ImsStreamMediaProfile profile);
+
+    /**
+     * Merges the active & hold call. When it succeeds, {@link Listener#callSessionMerged}
+     * is called.
+     *
+     * @see Listener#callSessionMerged, Listener#callSessionMergeFailed
+     */
+    void merge();
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be updated
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+     * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
+     */
+    void update(int callType, in ImsStreamMediaProfile profile);
+
+    /**
+     * Extends this call to the conference call with the specified recipients.
+     *
+     * @param participants participant list to be invited to the conference call after extending the call
+     * @see Listener#sessionConferenceExtened, Listener#sessionConferenceExtendFailed
+     */
+    void extendToConference(in String[] participants);
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     * @param participants participant list to be invited to the conference call
+     * @see Listener#sessionInviteParticipantsRequestDelivered,
+     *      Listener#sessionInviteParticipantsRequestFailed
+     */
+    void inviteParticipants(in String[] participants);
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     * @param participants participant list to be removed from the conference call
+     * @see Listener#sessionRemoveParticipantsRequestDelivered,
+     *      Listener#sessionRemoveParticipantsRequestFailed
+     */
+    void removeParticipants(in String[] participants);
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+     * @param duration the interval in milli-seconds between the DTMFs
+     */
+    void sendDtmf(int code, int duration);
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    void sendUssd(String ussdMessage);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
new file mode 100644
index 0000000..f36cf39
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -0,0 +1,99 @@
+/*
+ * 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.ims.internal;
+
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.internal.IImsCallSession;
+
+/**
+ * A listener type for receiving notification on IMS call session events.
+ * When an event is generated for an {@link IImsCallSession}, the application is notified
+ * by having one of the methods called on the {@link IImsCallSessionListener}.
+ * {@hide}
+ */
+interface IImsCallSessionListener {
+    /**
+     * Notifies the result of the basic session operation (setup / terminate).
+     */
+    void callSessionProgressing(in IImsCallSession session, in ImsStreamMediaProfile profile);
+    void callSessionStarted(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionStartFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionTerminated(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the result of the call hold/resume operation.
+     */
+    void callSessionHeld(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionHoldFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionHoldReceived(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionResumed(in IImsCallSession session, in ImsCallProfile profile);
+    void callSessionResumeFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+    void callSessionResumeReceived(in IImsCallSession session, in ImsCallProfile profile);
+
+    /**
+     * Notifiies the result of call merge operation.
+     */
+    void callSessionMerged(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+    void callSessionMergeFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the result of call upgrade / downgrade or any other call updates.
+     */
+    void callSessionUpdated(in IImsCallSession session,
+            in ImsCallProfile profile);
+    void callSessionUpdateFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionUpdateReceived(in IImsCallSession session,
+            in ImsCallProfile profile);
+
+    /**
+     * Notifies the result of conference extension.
+     */
+    void callSessionConferenceExtended(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+    void callSessionConferenceExtendFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionConferenceExtendReceived(in IImsCallSession session,
+            in IImsCallSession newSession, in ImsCallProfile profile);
+
+    /**
+     * Notifies the result of the participant invitation / removal to/from the conference session.
+     */
+    void callSessionInviteParticipantsRequestDelivered(in IImsCallSession session);
+    void callSessionInviteParticipantsRequestFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+    void callSessionRemoveParticipantsRequestDelivered(in IImsCallSession session);
+    void callSessionRemoveParticipantsRequestFailed(in IImsCallSession session,
+            in ImsReasonInfo reasonInfo);
+
+    /**
+     * Notifies the changes of the conference info. in the conference session.
+     */
+    void callSessionConferenceStateUpdated(in IImsCallSession session,
+            in ImsConferenceState state);
+
+    /**
+     * Notifies the incoming USSD message.
+     */
+    void callSessionUssdMessageReceived(in IImsCallSession session,
+            int mode, String ussdMessage);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
new file mode 100644
index 0000000..5f243a0
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.ims.internal;
+
+/**
+ * A listener type for receiving notifications about the changes to
+ * the IMS connection(registration).
+ *
+ * {@hide}
+ */
+interface IImsRegistrationListener {
+    /**
+     * Notifies the application when the device is connected to the IMS network.
+     */
+    void registrationConnected();
+
+    /**
+     * Notifies the application when the device is disconnected from the IMS network.
+     */
+    void registrationDisconnected();
+
+    /**
+     * Notifies the application when its suspended IMS connection is resumed,
+     * meaning the connection now allows throughput.
+     */
+    void registrationResumed();
+
+    /**
+     * Notifies the application when its current IMS connection is suspended,
+     * meaning there is no data throughput.
+     */
+    void registrationSuspended();
+
+    /**
+     * Notifies the application when its current IMS connection is updated
+     * since the service setting is changed or the service is added/removed.
+     *
+     * @param serviceClass a service class specified in {@link ImsServiceClass}
+     * @param event an event type when this callback is called
+     *    If {@code event} is 0, meaning the specified service is removed from the IMS connection.
+     *    Else ({@code event} is 1), meaning the specified service is added to the IMS connection.
+     */
+    void registrationServiceCapabilityChanged(int serviceClass, int event);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
new file mode 100644
index 0000000..bac5651
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsService {
+    int open(int serviceClass, in PendingIntent incomingCallIntent,
+            in IImsRegistrationListener listener);
+    void close(int serviceId);
+    boolean isConnected(int serviceId, int serviceType, int callType);
+    boolean isOpened(int serviceId);
+    void setRegistrationListener(int serviceId, in IImsRegistrationListener listener);
+
+    ImsCallProfile createCallProfile(int serviceId, int serviceType, int callType);
+
+    IImsCallSession createCallSession(int serviceId, in ImsCallProfile profile,
+            in IImsCallSessionListener listener);
+    IImsCallSession getPendingCallSession(int serviceId, String callId);
+
+    /**
+     * Ut interface for the supplementary service configuration.
+     */
+    IImsUt getUtInterface(int serviceId);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl b/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl
new file mode 100644
index 0000000..459c685
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.ims.internal;
+
+/**
+ *
+ * {@hide}
+ */
+interface IImsStreamMediaSession {
+    void close();
+}
diff --git a/telephony/java/com/android/ims/internal/IImsUt.aidl b/telephony/java/com/android/ims/internal/IImsUt.aidl
new file mode 100644
index 0000000..32929ed
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsUt.aidl
@@ -0,0 +1,73 @@
+/*
+ * 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.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Provides the Ut interface interworking to get/set the supplementary service configuration.
+ *
+ * {@hide}
+ */
+interface IImsUt {
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    void close();
+
+    /**
+     * Retrieves the configuration of the call barring.
+     */
+    int queryCallBarring(int cbType);
+
+    /**
+     * Retrieves the configuration of the call forward.
+     */
+    int queryCallForward(int condition, String number);
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     */
+    int queryCallWaiting();
+
+    /**
+     * Updates or retrieves the supplementary service configuration.
+     */
+    int transact(in Bundle ssInfo);
+
+    /**
+     * Updates the configuration of the call barring.
+     */
+    int updateCallBarring(int cbType, boolean enable);
+
+    /**
+     * Updates the configuration of the call forward.
+     */
+    int updateCallForward(int action, int condition, String number, int timeSeconds);
+
+    /**
+     * Updates the configuration of the call waiting.
+     */
+    int updateCallWaiting(boolean enable);
+
+    /**
+     * Sets the listener.
+     */
+    void setListener(in IImsUtListener listener);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
new file mode 100644
index 0000000..3f1b5a7
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsSsInfo;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsUtListener {
+    /**
+     * Notifies the result of the supplementary service configuration udpate.
+     */
+    void utConfigurationUpdated(in IImsUt ut, int id);
+    void utConfigurationUpdateFailed(in IImsUt ut, int id, int errorCode);
+
+    /**
+     * Notifies the result of the supplementary service configuration query.
+     */
+    void utConfigurationQueried(in IImsUt ut, int id, in Bundle ssInfo);
+    void utConfigurationQueryFailed(in IImsUt ut, int id, int errorCode);
+
+    /**
+     * Notifies the status of the call barring supplementary service.
+     */
+    void utConfigurationCallBarringQueried(in IImsUt ut,
+            int id, in ImsSsInfo[] cbInfo);
+
+    /**
+     * Notifies the status of the call forwarding supplementary service.
+     */
+    void utConfigurationCallForwardQueried(in IImsUt ut,
+            int id, in ImsCallForwardInfo[] cfInfo);
+
+    /**
+     * Notifies the status of the call waiting supplementary service.
+     */
+    void utConfigurationCallWaitingQueried(in IImsUt ut,
+            int id, in ImsSsInfo[] cwInfo);
+}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index f8dd7cf..745c9d0 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -37,6 +37,9 @@
 import com.android.i18n.phonenumbers.NumberParseException;
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import android.telephony.SubscriptionManager;
+
+import android.telephony.TelephonyManager;
 
 import java.util.Locale;
 
@@ -233,6 +236,7 @@
                 info.contactExists = true;
             }
             cursor.close();
+            cursor = null;
         }
 
         info.needUpdate = false;
@@ -269,6 +273,23 @@
     public static CallerInfo getCallerInfo(Context context, String number) {
         if (VDBG) Rlog.v(TAG, "getCallerInfo() based on number...");
 
+        long subId = SubscriptionManager.getDefaultSubId();
+        return getCallerInfo(context, number, subId);
+    }
+
+    /**
+     * getCallerInfo given a phone number and subscription, look up in the call-log database
+     * for the matching caller id info.
+     * @param context the context used to get the ContentResolver
+     * @param number the phone number used to lookup caller id
+     * @param subId the subscription for checking for if voice mail number or not
+     * @return the CallerInfo which contains the caller id for the given
+     * number. The returned CallerInfo is null if no number is supplied. If
+     * a matching number is not found, then a generic caller info is returned,
+     * with all relevant fields empty or null.
+     */
+    public static CallerInfo getCallerInfo(Context context, String number, long subId) {
+
         if (TextUtils.isEmpty(number)) {
             return null;
         }
@@ -278,7 +299,7 @@
         // shortcut and skip the query.
         if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
             return new CallerInfo().markAsEmergency(context);
-        } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
+        } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
             return new CallerInfo().markAsVoiceMail();
         }
 
@@ -400,10 +421,17 @@
     // TODO: As in the emergency number handling, we end up writing a
     // string in the phone number field.
     /* package */ CallerInfo markAsVoiceMail() {
+
+        long subId = SubscriptionManager.getDefaultSubId();
+        return markAsVoiceMail(subId);
+
+    }
+
+    /* package */ CallerInfo markAsVoiceMail(long subId) {
         mIsVoiceMail = true;
 
         try {
-            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag();
+            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag(subId);
 
             phoneNumber = voiceMailLabel;
         } catch (SecurityException se) {
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 34fed5e..fe403d9 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -24,12 +24,15 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 
 /**
  * Helper class to make it easier to run asynchronous caller-id lookup queries.
@@ -75,6 +78,8 @@
         public Object cookie;
         public int event;
         public String number;
+
+        public long subId;
     }
 
 
@@ -207,11 +212,17 @@
                 // However, if there is any code that calls this method, we should
                 // check the parameters to make sure they're viable.
                 if (DBG) Rlog.d(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
+                if (cursor != null) {
+                    cursor.close();
+                }
                 return;
             }
 
             if (cw.event == EVENT_END_OF_QUEUE) {
                 release();
+                if (cursor != null) {
+                    cursor.close();
+                }
                 return;
             }
 
@@ -232,7 +243,7 @@
                     // comments at the top of CallerInfo class).
                     mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
                 } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
-                    mCallerInfo = new CallerInfo().markAsVoiceMail();
+                    mCallerInfo = new CallerInfo().markAsVoiceMail(cw.subId);
                 } else {
                     mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
                     if (DBG) Rlog.d(LOG_TAG, "==> Got mCallerInfo: " + mCallerInfo);
@@ -289,6 +300,10 @@
                              " for token: " + token + mCallerInfo);
                 cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
             }
+
+            if (cursor != null) {
+               cursor.close();
+            }
         }
     }
 
@@ -334,6 +349,25 @@
      */
     public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
             OnQueryCompleteListener listener, Object cookie) {
+
+        long subId = SubscriptionManager.getDefaultSubId();
+        return startQuery(token, context, number, listener, cookie, subId);
+    }
+
+    /**
+     * Factory method to start the query based on a number with specific subscription.
+     *
+     * Note: if the number contains an "@" character we treat it
+     * as a SIP address, and look it up directly in the Data table
+     * rather than using the PhoneLookup table.
+     * TODO: But eventually we should expose two separate methods, one for
+     * numbers and one for SIP addresses, and then have
+     * PhoneUtils.startGetCallerInfo() decide which one to call based on
+     * the phone type of the incoming connection.
+     */
+    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+            OnQueryCompleteListener listener, Object cookie, long subId) {
+
         if (DBG) {
             Rlog.d(LOG_TAG, "##### CallerInfoAsyncQuery startQuery()... #####");
             Rlog.d(LOG_TAG, "- number: " + /*number*/ "xxxxxxx");
@@ -397,11 +431,12 @@
         cw.listener = listener;
         cw.cookie = cookie;
         cw.number = number;
+        cw.subId = subId;
 
         // check to see if these are recognized numbers, and use shortcuts if we can.
         if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
             cw.event = EVENT_EMERGENCY_NUMBER;
-        } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
+        } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
             cw.event = EVENT_VOICEMAIL_NUMBER;
         } else {
             cw.event = EVENT_NEW_QUERY;
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/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
new file mode 100644
index 0000000..a745420
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.telephony;
+
+import android.app.PendingIntent;
+
+/**
+ * Service interface to handle MMS API requests
+ */
+interface IMms {
+    /**
+     * Send an MMS message
+     *
+     * @param callingPkg the package name of the calling app
+     * @param pdu the MMS message encoded in standard MMS PDU format
+     * @param locationUrl the optional location url for where this message should be sent to
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed
+     */
+    void sendMessage(String callingPkg, in byte[] pdu, String locationUrl,
+            in PendingIntent sentIntent);
+
+    /**
+     * Download an MMS message using known location and transaction id
+     *
+     * @param callingPkg the package name of the calling app
+     * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
+     *  from the MMS WAP push notification
+     * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is downloaded, or the download is failed
+     */
+    void downloadMessage(String callingPkg, String locationUrl, in PendingIntent downloadedIntent);
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 3f36645..a95336e 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -23,6 +23,7 @@
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.VoLteServiceState;
 
 oneway interface IPhoneStateListener {
     void onServiceStateChanged(in ServiceState serviceState);
@@ -41,5 +42,6 @@
     void onPreciseCallStateChanged(in PreciseCallState callState);
     void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
     void onDataConnectionRealTimeInfoChanged(in DataConnectionRealTimeInfo dcRtInfo);
+    void onVoLteServiceStateChanged(in VoLteServiceState lteState);
 }
 
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 03940dc..4734965 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -28,6 +28,13 @@
     String getDeviceId();
 
     /**
+     * Retrieves the unique device ID of a subId for the device, e.g., IMEI
+     * for GSM phones.
+     */
+    String getDeviceIdUsingSubId(long subId);
+
+
+    /**
      * Retrieves the software version number for the device, e.g., IMEI/SV
      * for GSM phones.
      */
@@ -39,46 +46,94 @@
     String getSubscriberId();
 
     /**
+     * Retrieves the unique subscriber ID of a given subId, e.g., IMSI for GSM phones.
+     */
+    String getSubscriberIdUsingSubId(long subId);
+
+    /**
      * Retrieves the Group Identifier Level1 for GSM phones.
      */
     String getGroupIdLevel1();
 
     /**
+     * Retrieves the Group Identifier Level1 for GSM phones of a subId.
+     */
+    String getGroupIdLevel1UsingSubId(long subId);
+
+    /**
      * Retrieves the serial number of the ICC, if applicable.
      */
     String getIccSerialNumber();
 
     /**
+     * Retrieves the serial number of a given subId.
+     */
+    String getIccSerialNumberUsingSubId(long subId);
+
+    /**
      * Retrieves the phone number string for line 1.
      */
     String getLine1Number();
 
     /**
+     * Retrieves the phone number string for line 1 of a subcription.
+     */
+    String getLine1NumberUsingSubId(long subId);
+
+
+    /**
      * Retrieves the alpha identifier for line 1.
      */
     String getLine1AlphaTag();
 
     /**
+     * Retrieves the alpha identifier for line 1 of a subId.
+     */
+    String getLine1AlphaTagUsingSubId(long subId);
+
+
+    /**
      * Retrieves MSISDN Number.
      */
     String getMsisdn();
 
     /**
+     * Retrieves the Msisdn of a subId.
+     */
+    String getMsisdnUsingSubId(long subId);
+
+    /**
      * Retrieves the voice mail number.
      */
     String getVoiceMailNumber();
 
     /**
+     * Retrieves the voice mail number of a given subId.
+     */
+    String getVoiceMailNumberUsingSubId(long subId);
+
+    /**
      * Retrieves the complete voice mail number.
      */
     String getCompleteVoiceMailNumber();
 
     /**
+     * Retrieves the complete voice mail number for particular subId
+     */
+    String getCompleteVoiceMailNumberUsingSubId(long subId);
+
+    /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
     String getVoiceMailAlphaTag();
 
     /**
+     * Retrieves the alpha identifier associated with the voice mail number
+     * of a subId.
+     */
+    String getVoiceMailAlphaTagUsingSubId(long subId);
+
+    /**
      * Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
      * @return the IMPI, or null if not present or not loaded
      */
@@ -96,4 +151,38 @@
      *      not present or not loaded
      */
     String[] getIsimImpu();
+
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     */
+    String getIsimIst();
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of PCSCF strings with one PCSCF per string, or null if
+     *      not present or not loaded
+     */
+    String[] getIsimPcscf();
+
+    /**
+     * TODO: Deprecate and remove this interface. Superceded by getIccsimChallengeResponse.
+     * Returns the response of ISIM Authetification through RIL.
+     * @return the response of ISIM Authetification, or null if
+     *     the Authentification hasn't been successed or isn't present iphonesubinfo.
+     */
+    String getIsimChallengeResponse(String nonce);
+
+    /**
+     * Returns the response of the SIM application on the UICC to authentication
+     * challenge/response algorithm. The data string and challenge response are
+     * Base64 encoded Strings.
+     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
+     *
+     * @param subId subscription ID to be queried
+     * @param appType ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @param data authentication challenge data
+     * @return challenge response
+     */
+    String getIccSimChallengeResponse(long subId, int appType, String data);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 3e8db06..53429b6 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -42,6 +42,13 @@
      List<SmsRawData> getAllMessagesFromIccEf(String callingPkg);
 
     /**
+     * Retrieves all messages currently stored on ICC.
+     * @param subId the subId id.
+     * @return list of SmsRawData of all sms on ICC
+     */
+    List<SmsRawData> getAllMessagesFromIccEfUsingSubId(in long subId, String callingPkg);
+
+    /**
      * Update the specified message on the ICC.
      *
      * @param messageIndex record index of message to update
@@ -56,6 +63,21 @@
             in byte[] pdu);
 
     /**
+     * Update the specified message on the ICC.
+     *
+     * @param messageIndex record index of message to update
+     * @param newStatus new message status (STATUS_ON_ICC_READ,
+     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+     * @param pdu the raw PDU to store
+     * @param subId the subId id.
+     * @return success or not
+     *
+     */
+     boolean updateMessageOnIccEfUsingSubId(in long subId, String callingPkg,
+             int messageIndex, int newStatus, in byte[] pdu);
+
+    /**
      * Copy a raw SMS PDU to the ICC.
      *
      * @param pdu the raw PDU to store
@@ -67,6 +89,19 @@
     boolean copyMessageToIccEf(String callingPkg, int status, in byte[] pdu, in byte[] smsc);
 
     /**
+     * Copy a raw SMS PDU to the ICC.
+     *
+     * @param pdu the raw PDU to store
+     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+     * @param subId the subId id.
+     * @return success or not
+     *
+     */
+    boolean copyMessageToIccEfUsingSubId(in long subId, String callingPkg, int status,
+            in byte[] pdu, in byte[] smsc);
+
+    /**
      * Send a data SMS.
      *
      * @param smsc the SMSC to send the message through, or NULL for the
@@ -93,6 +128,34 @@
             in byte[] data, in PendingIntent sentIntent, in PendingIntent deliveryIntent);
 
     /**
+     * Send a data SMS.
+     *
+     * @param smsc the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param data the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param subId the subId id.
+     */
+    void sendDataUsingSubId(long subId, String callingPkg, in String destAddr,
+            in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent,
+            in PendingIntent deliveryIntent);
+
+    /**
      * Send an SMS.
      *
      * @param smsc the SMSC to send the message through, or NULL for the
@@ -119,6 +182,58 @@
             in PendingIntent sentIntent, in PendingIntent deliveryIntent);
 
     /**
+     * Send an SMS.
+     *
+     * @param smsc the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param subId the subId on which the SMS has to be sent.
+     */
+    void sendTextUsingSubId(in long subId, String callingPkg, in String destAddr,
+            in String scAddr, in String text, 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);
+
+    /**
+     * Update the status of a pending (send-by-IP) SMS message and resend by PSTN if necessary.
+     * This outbound message was handled by the carrier app. If the carrier app fails to send
+     * this message, it would be resent by PSTN.
+     *
+     * @param messageRef the reference number of the SMS message.
+     * @param success True if and only if the message was sent successfully. If its value is
+     *  false, this message should be resent via PSTN.
+     */
+    void updateSmsSendStatus(int messageRef, boolean success);
+
+    /**
      * Send a multi-part text based SMS.
      *
      * @param destinationAddress the address to send the message to
@@ -145,6 +260,34 @@
             in List<PendingIntent> deliveryIntents);
 
     /**
+     * Send a multi-part text based SMS.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK<code> for success,
+     *   or one of these errors:
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
+     *   <code>RESULT_ERROR_RADIO_OFF</code>
+     *   <code>RESULT_ERROR_NULL_PDU</code>.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param subId the subId on which the SMS has to be sent.
+     */
+    void sendMultipartTextUsingSubId(in long subId, String callingPkg,
+            in String destinationAddress, in String scAddress,
+            in List<String> parts, in List<PendingIntent> sentIntents,
+            in List<PendingIntent> deliveryIntents);
+
+    /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier. Note that if two different clients enable the same
      * message identifier, they must both disable it for the device to stop
@@ -159,6 +302,21 @@
     boolean enableCellBroadcast(int messageIdentifier);
 
     /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param subId for which the broadcast has to be enabled
+     * @return true if successful, false otherwise
+     *
+     * @see #disableCellBroadcast(int)
+     */
+    boolean enableCellBroadcastUsingSubId(in long subId, int messageIdentifier);
+
+    /**
      * Disable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier. Note that if two different clients enable the same
      * message identifier, they must both disable it for the device to stop
@@ -172,6 +330,21 @@
      */
     boolean disableCellBroadcast(int messageIdentifier);
 
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param subId for which the broadcast has to be disabled
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcast(int)
+     */
+    boolean disableCellBroadcastUsingSubId(in long subId, int messageIdentifier);
+
     /*
      * Enable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier range. Note that if two different clients enable
@@ -188,6 +361,23 @@
      */
     boolean enableCellBroadcastRange(int startMessageId, int endMessageId);
 
+    /*
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param subId for which the broadcast has to be enabled
+     * @return true if successful, false otherwise
+     *
+     * @see #disableCellBroadcastRange(int, int)
+     */
+    boolean enableCellBroadcastRangeUsingSubId(long subId, int startMessageId, int endMessageId);
+
     /**
      * Disable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier range. Note that if two different clients enable
@@ -205,18 +395,52 @@
     boolean disableCellBroadcastRange(int startMessageId, int endMessageId);
 
     /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) or
+     *   C.R1001-G (3GPP2)
+     * @param subId for which the broadcast has to be disabled
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int, int)
+     */
+    boolean disableCellBroadcastRangeUsingSubId(long subId, int startMessageId,
+            int endMessageId);
+
+    /**
      * Returns the premium SMS send permission for the specified package.
      * Requires system permission.
      */
     int getPremiumSmsPermission(String packageName);
 
     /**
+     * Returns the premium SMS send permission for the specified package.
+     * Requires system permission.
+     */
+    int getPremiumSmsPermissionUsingSubId(long subId, String packageName);
+
+    /**
      * Set the SMS send permission for the specified package.
      * Requires system permission.
      */
     void setPremiumSmsPermission(String packageName, int permission);
 
     /**
+     * Set the SMS send permission for the specified package.
+     * Requires system permission.
+     */
+     /**
+     * Set the SMS send permission for the specified package.
+     * Requires system permission.
+     */
+    void setPremiumSmsPermissionUsingSubId(long subId, String packageName, int permission);
+
+    /**
      * SMS over IMS is supported if IMS is registered and SMS is supported
      * on IMS.
      *
@@ -227,6 +451,23 @@
     boolean isImsSmsSupported();
 
     /**
+     * SMS over IMS is supported if IMS is registered and SMS is supported
+     * on IMS.
+     * @param subId for subId which isImsSmsSupported is queried
+     * @return true if SMS over IMS is supported, false otherwise
+     *
+     * @see #getImsSmsFormat()
+     */
+    boolean isImsSmsSupportedUsingSubId(long subId);
+
+    /*
+     * get user prefered SMS subId
+     * @return subId id
+     */
+    long getPreferredSmsSubscription();
+
+
+    /**
      * Gets SMS format supported on IMS.  SMS over IMS format is
      * either 3GPP or 3GPP2.
      *
@@ -237,4 +478,25 @@
      * @see #isImsSmsSupported()
      */
     String getImsSmsFormat();
+
+    /**
+     * Gets SMS format supported on IMS.  SMS over IMS format is
+     * either 3GPP or 3GPP2.
+     * @param subId for subId which getImsSmsFormat is queried
+     * @return android.telephony.SmsMessage.FORMAT_3GPP,
+     *         android.telephony.SmsMessage.FORMAT_3GPP2
+     *      or android.telephony.SmsMessage.FORMAT_UNKNOWN
+     *
+     * @see #isImsSmsSupported()
+     */
+    String getImsSmsFormatUsingSubId(long subId);
+
+
+
+
+    /*
+     * Get SMS prompt property,  enabled or not
+     * @return true if enabled, false otherwise
+     */
+    boolean isSMSPromptEnabled();
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
new file mode 100755
index 0000000..6021ccf
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -0,0 +1,153 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* 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.telephony;
+
+import android.app.PendingIntent;
+import android.telephony.SubInfoRecord;
+
+interface ISub {
+    /**
+     * Get the SubInfoRecord according to an index
+     * @param context Context provided by caller
+     * @param subId The unique SubInfoRecord index in database
+     * @return SubInfoRecord, maybe null
+     */
+    SubInfoRecord getSubInfoUsingSubId(long subId);
+
+    /**
+     * Get the SubInfoRecord according to an IccId
+     * @param context Context provided by caller
+     * @param iccId the IccId of SIM card
+     * @return SubInfoRecord, maybe null
+     */
+    List<SubInfoRecord> getSubInfoUsingIccId(String iccId);
+
+    /**
+     * Get the SubInfoRecord according to slotId
+     * @param context Context provided by caller
+     * @param slotId the slot which the SIM is inserted
+     * @return SubInfoRecord, maybe null
+     */
+    List<SubInfoRecord> getSubInfoUsingSlotId(int slotId);
+
+    /**
+     * Get all the SubInfoRecord(s) in subinfo database
+     * @param context Context provided by caller
+     * @return Array list of all SubInfoRecords in database, include thsoe that were inserted before
+     */
+    List<SubInfoRecord> getAllSubInfoList();
+
+    /**
+     * Get the SubInfoRecord(s) of the currently inserted SIM(s)
+     * @param context Context provided by caller
+     * @return Array list of currently inserted SubInfoRecord(s)
+     */
+    List<SubInfoRecord> getActivatedSubInfoList();
+
+    /**
+     * Get the SUB count of all SUB(s) in subinfo database
+     * @param context Context provided by caller
+     * @return all SIM count in database, include what was inserted before
+     */
+    int getAllSubInfoCount();
+
+    /**
+     * Add a new SubInfoRecord to subinfo database if needed
+     * @param context Context provided by caller
+     * @param iccId the IccId of the SIM card
+     * @param slotId the slot which the SIM is inserted
+     * @return the URL of the newly created row or the updated row
+     */
+    int addSubInfoRecord(String iccId, int slotId);
+
+    /**
+     * Set SIM color by simInfo index
+     * @param context Context provided by caller
+     * @param color the color of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    int setColor(int color, long subId);
+
+    /**
+     * Set display name by simInfo index
+     * @param context Context provided by caller
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    int setDisplayName(String displayName, long subId);
+
+    /**
+     * Set display name by simInfo index with name source
+     * @param context Context provided by caller
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @param nameSource, 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT
+     * @return the number of records updated
+     */
+    int setDisplayNameUsingSrc(String displayName, long subId, long nameSource);
+
+    /**
+     * Set phone number by subId
+     * @param context Context provided by caller
+     * @param number the phone number of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    int setDispalyNumber(String number, long subId);
+
+    /**
+     * Set number display format. 0: none, 1: the first four digits, 2: the last four digits
+     * @param context Context provided by caller
+     * @param format the display format of phone number
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    int setDisplayNumberFormat(int format, long subId);
+
+    /**
+     * Set data roaming by simInfo index
+     * @param context Context provided by caller
+     * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    int setDataRoaming(int roaming, long subId);
+
+    int getSlotId(long subId);
+
+    long[] getSubId(int slotId);
+
+    long getDefaultSubId();
+
+    int clearSubInfo();
+
+    int getPhoneId(long subId);
+
+    /**
+     * Get the default data subscription
+     * @return Id of the data subscription
+     */
+    long getDefaultDataSubId();
+
+    void setDefaultDataSubId(long subId);
+
+    long getDefaultVoiceSubId();
+
+    void setDefaultVoiceSubId(long subId);
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f9462f2..beee616 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -16,14 +16,11 @@
 
 package com.android.internal.telephony;
 
-import android.content.ComponentName;
 import android.os.Bundle;
-import android.telephony.CellInfo;
+import java.util.List;
 import android.telephony.NeighboringCellInfo;
+import android.telephony.CellInfo;
 
-import java.util.List;
-
-import java.util.List;
 
 /**
  * Interface used to interact with the phone.  Mostly this is used by the
@@ -56,6 +53,13 @@
     boolean endCall();
 
     /**
+     * End call on particular subId or go to the Home screen
+     * @param subId user preferred subId.
+     * @return whether it hung up
+     */
+    boolean endCallUsingSubId(long subId);
+
+    /**
      * Answer the currently-ringing call.
      *
      * If there's already a current active call, that call will be
@@ -92,6 +96,23 @@
     boolean isOffhook();
 
     /**
+     * Check if a particular subId has an active or holding call
+     *
+     * @param subId user preferred subId.
+     * @return true if the phone state is OFFHOOK.
+     */
+    boolean isOffhookUsingSubId(long subId);
+
+    /**
+     * Check if an incoming phone call is ringing or call waiting
+     * on a particular subId.
+     *
+     * @param subId user preferred subId.
+     * @return true if the phone state is RINGING.
+     */
+    boolean isRingingUsingSubId(long subId);
+
+    /**
      * Check if an incoming phone call is ringing or call waiting.
      * @return true if the phone state is RINGING.
      */
@@ -104,12 +125,27 @@
     boolean isIdle();
 
     /**
+     * Check if the phone is idle on a particular subId.
+     *
+     * @param subId user preferred subId.
+     * @return true if the phone state is IDLE.
+     */
+    boolean isIdleUsingSubId(long subId);
+
+    /**
      * Check to see if the radio is on or not.
      * @return returns true if the radio is on.
      */
     boolean isRadioOn();
 
     /**
+     * Check to see if the radio is on or not on particular subId.
+     * @param subId user preferred subId.
+     * @return returns true if the radio is on.
+     */
+    boolean isRadioOnUsingSubId(long subId);
+
+    /**
      * Check if the SIM pin lock is enabled.
      * @return true if the SIM pin lock is enabled.
      */
@@ -128,6 +164,15 @@
     boolean supplyPin(String pin);
 
     /**
+     * Supply a pin to unlock the SIM for particular subId.
+     * Blocks until a result is determined.
+     * @param pin The pin to check.
+     * @param subId user preferred subId.
+     * @return whether the operation was a success.
+     */
+    boolean supplyPinUsingSubId(long subId, String pin);
+
+    /**
      * Supply puk to unlock the SIM and set SIM pin to new pin.
      *  Blocks until a result is determined.
      * @param puk The puk to check.
@@ -137,6 +182,16 @@
     boolean supplyPuk(String puk, String pin);
 
     /**
+     * Supply puk to unlock the SIM and set SIM pin to new pin.
+     *  Blocks until a result is determined.
+     * @param puk The puk to check.
+     *        pin The new pin to be set in SIM
+     * @param subId user preferred subId.
+     * @return whether the operation was a success.
+     */
+    boolean supplyPukUsingSubId(long subId, String puk, String pin);
+
+    /**
      * Supply a pin to unlock the SIM.  Blocks until a result is determined.
      * Returns a specific success/error code.
      * @param pin The pin to check.
@@ -146,6 +201,15 @@
     int[] supplyPinReportResult(String pin);
 
     /**
+     * Supply a pin to unlock the SIM.  Blocks until a result is determined.
+     * Returns a specific success/error code.
+     * @param pin The pin to check.
+     * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code
+     *         retValue[1] = number of attempts remaining if known otherwise -1
+     */
+    int[] supplyPinReportResultUsingSubId(long subId, String pin);
+
+    /**
      * Supply puk to unlock the SIM and set SIM pin to new pin.
      * Blocks until a result is determined.
      * Returns a specific success/error code
@@ -157,6 +221,17 @@
     int[] supplyPukReportResult(String puk, String pin);
 
     /**
+     * Supply puk to unlock the SIM and set SIM pin to new pin.
+     * Blocks until a result is determined.
+     * Returns a specific success/error code
+     * @param puk The puk to check
+     *        pin The pin to check.
+     * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code
+     *         retValue[1] = number of attempts remaining if known otherwise -1
+     */
+    int[] supplyPukReportResultUsingSubId(long subId, String puk, String pin);
+
+    /**
      * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
      * without SEND (so <code>dial</code> is not appropriate).
      *
@@ -166,16 +241,38 @@
     boolean handlePinMmi(String dialString);
 
     /**
+     * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
+     * without SEND (so <code>dial</code> is not appropriate) for
+     * a particular subId.
+     * @param dialString the MMI command to be executed.
+     * @param subId user preferred subId.
+     * @return true if MMI command is executed.
+     */
+    boolean handlePinMmiUsingSubId(long subId, String dialString);
+
+    /**
      * Toggles the radio on or off.
      */
     void toggleRadioOnOff();
 
     /**
+     * Toggles the radio on or off on particular subId.
+     * @param subId user preferred subId.
+     */
+    void toggleRadioOnOffUsingSubId(long subId);
+
+    /**
      * Set the radio to on or off
      */
     boolean setRadio(boolean turnOn);
 
     /**
+     * Set the radio to on or off on particular subId.
+     * @param subId user preferred subId.
+     */
+    boolean setRadioUsingSubId(long subId, boolean turnOn);
+
+    /**
      * Set the radio to on or off unconditionally
      */
     boolean setRadioPower(boolean turnOn);
@@ -186,24 +283,32 @@
     void updateServiceLocation();
 
     /**
+     * Request to update location information for a subscrition in service state
+     * @param subId user preferred subId.
+     */
+    void updateServiceLocationUsingSubId(long subId);
+
+    /**
      * Enable location update notifications.
      */
     void enableLocationUpdates();
 
     /**
+     * Enable location update notifications.
+     * @param subId user preferred subId.
+     */
+    void enableLocationUpdatesUsingSubId(long subId);
+
+    /**
      * Disable location update notifications.
      */
     void disableLocationUpdates();
 
     /**
-     * Enable a specific APN type.
+     * Disable location update notifications.
+     * @param subId user preferred subId.
      */
-    int enableApnType(String type);
-
-    /**
-     * Disable a specific APN type.
-     */
-    int disableApnType(String type);
+    void disableLocationUpdatesUsingSubId(long subId);
 
     /**
      * Allow mobile data connections.
@@ -228,6 +333,12 @@
     List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
 
      int getCallState();
+
+    /**
+     * Returns the call state for a subId.
+     */
+     int getCallStateUsingSubId(long subId);
+
      int getDataActivity();
      int getDataState();
 
@@ -239,11 +350,25 @@
     int getActivePhoneType();
 
     /**
+     * Returns the current active phone type as integer for particular subId.
+     * Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE
+     * and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE
+     * @param subId user preferred subId.
+     */
+    int getActivePhoneTypeUsingSubId(long subId);
+
+    /**
      * Returns the CDMA ERI icon index to display
      */
     int getCdmaEriIconIndex();
 
     /**
+     * Returns the CDMA ERI icon index to display on particular subId.
+     * @param subId user preferred subId.
+     */
+    int getCdmaEriIconIndexUsingSubId(long subId);
+
+    /**
      * Returns the CDMA ERI icon mode,
      * 0 - ON
      * 1 - FLASHING
@@ -251,11 +376,25 @@
     int getCdmaEriIconMode();
 
     /**
+     * Returns the CDMA ERI icon mode on particular subId,
+     * 0 - ON
+     * 1 - FLASHING
+     * @param subId user preferred subId.
+     */
+    int getCdmaEriIconModeUsingSubId(long subId);
+
+    /**
      * Returns the CDMA ERI text,
      */
     String getCdmaEriText();
 
     /**
+     * Returns the CDMA ERI text for particular subId,
+     * @param subId user preferred subId.
+     */
+    String getCdmaEriTextUsingSubId(long subId);
+
+    /**
      * Returns true if OTA service provisioning needs to run.
      * Only relevant on some technologies, others will always
      * return false.
@@ -268,26 +407,61 @@
     int getVoiceMessageCount();
 
     /**
+     * Returns the unread count of voicemails for a subId.
+     * @param subId user preferred subId.
+     * Returns the unread count of voicemails
+     */
+    int getVoiceMessageCountUsingSubId(long subId);
+
+    /**
       * Returns the network type for data transmission
       */
     int getNetworkType();
 
     /**
+     * Returns the network type of a subId.
+     * @param subId user preferred subId.
+     * Returns the network type
+     */
+    int getNetworkTypeUsingSubId(long subId);
+
+    /**
       * Returns the network type for data transmission
       */
     int getDataNetworkType();
 
     /**
+      * Returns the data network type of a subId
+      * @param subId user preferred subId.
+      * Returns the network type
+      */
+    int getDataNetworkTypeUsingSubId(long subId);
+
+    /**
       * Returns the network type for voice
       */
     int getVoiceNetworkType();
 
     /**
+      * Returns the voice network type of a subId
+      * @param subId user preferred subId.
+      * Returns the network type
+      */
+    int getVoiceNetworkTypeUsingSubId(long subId);
+
+    /**
      * Return true if an ICC card is present
      */
     boolean hasIccCard();
 
     /**
+     * Return true if an ICC card is present for a subId.
+     * @param slotId user preferred slotId.
+     * Return true if an ICC card is present
+     */
+    boolean hasIccCardUsingSlotId(long slotId);
+
+    /**
      * Return if the current radio is LTE on CDMA. This
      * is a tri-state return value as for a period of time
      * the mode may be unknown.
@@ -298,6 +472,16 @@
     int getLteOnCdmaMode();
 
     /**
+     * Return if the current radio is LTE on CDMA. This
+     * is a tri-state return value as for a period of time
+     * the mode may be unknown.
+     *
+     * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
+     * or {@link PHone#LTE_ON_CDMA_TRUE}
+     */
+    int getLteOnCdmaModeUsingSubId(long subId);
+
+    /**
      * Returns the all observed cell information of the device.
      */
     List<CellInfo> getAllCellInfo();
@@ -308,6 +492,120 @@
     void setCellInfoListRate(int rateInMillis);
 
     /**
+     * get default sim
+     * @return sim id
+     */
+    int getDefaultSim();
+
+    /**
+     * 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
@@ -320,5 +618,16 @@
      * @return true on enabled
      */
     boolean getDataEnabled();
+
+    /**
+     * Get P-CSCF address from PCO after data connection is established or modified.
+     */
+    String[] getPcscfAddress();
+
+    /**
+     * Set IMS registration state
+     */
+    void setImsRegistrationState(boolean registered);
+
 }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index b104c11..fd2d1c7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -24,22 +24,36 @@
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.CellInfo;
+import android.telephony.VoLteServiceState;
 import com.android.internal.telephony.IPhoneStateListener;
 
 interface ITelephonyRegistry {
     void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
-
+    void listenUsingSubId(in long subId, String pkg, IPhoneStateListener callback, int events,
+            boolean notifyNow);
     void notifyCallState(int state, String incomingNumber);
+    void notifyCallStateUsingSubId(in long subId, int state, String incomingNumber);
     void notifyServiceState(in ServiceState state);
+    void notifyServiceStateUsingSubId(in long subId, in ServiceState state);
     void notifySignalStrength(in SignalStrength signalStrength);
+    void notifySignalStrengthUsingSubId(in long subId, in SignalStrength signalStrength);
     void notifyMessageWaitingChanged(boolean mwi);
+    void notifyMessageWaitingChangedUsingSubId(in long subId, boolean mwi);
     void notifyCallForwardingChanged(boolean cfi);
+    void notifyCallForwardingChangedUsingSubId(in long subId, boolean cfi);
     void notifyDataActivity(int state);
+    void notifyDataActivityUsingSubId(in long subId, int state);
     void notifyDataConnection(int state, boolean isDataConnectivityPossible,
             String reason, String apn, String apnType, in LinkProperties linkProperties,
             in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
+    void notifyDataConnectionUsingSubId(long subId, int state, boolean isDataConnectivityPossible,
+            String reason, String apn, String apnType, in LinkProperties linkProperties,
+            in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
     void notifyDataConnectionFailed(String reason, String apnType);
+    void notifyDataConnectionFailedUsingSubId(long subId, String reason, String apnType);
     void notifyCellLocation(in Bundle cellLocation);
+    void notifyCellLocationUsingSubId(in long subId, in Bundle cellLocation);
     void notifyOtaspChanged(in int otaspMode);
     void notifyCellInfo(in List<CellInfo> cellInfo);
     void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
@@ -47,5 +61,7 @@
     void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause);
     void notifyPreciseDataConnectionFailed(String reason, String apnType, String apn,
             String failCause);
+    void notifyCellInfoUsingSubId(in long subId, in List<CellInfo> cellInfo);
     void notifyDataConnectionRealTimeInfo(in DataConnectionRealTimeInfo dcRtInfo);
+    void notifyVoLteServiceStateChanged(in VoLteServiceState lteState);
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 08f4379..aecf955 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -58,6 +58,7 @@
     public static final int PHONE_TYPE_CDMA = RILConstants.CDMA_PHONE;
     public static final int PHONE_TYPE_SIP = RILConstants.SIP_PHONE;
     public static final int PHONE_TYPE_THIRD_PARTY = RILConstants.THIRD_PARTY_PHONE;
+    public static final int PHONE_TYPE_IMS = RILConstants.IMS_PHONE;
 
     // Modes for LTE_ON_CDMA
     public static final int LTE_ON_CDMA_UNKNOWN = RILConstants.LTE_ON_CDMA_UNKNOWN;
@@ -133,4 +134,55 @@
     /** APN type for IA Initial Attach APN */
     public static final String APN_TYPE_IA = "ia";
 
+    // FIXME: This looks to be used as default phoneId, rename
+    // or use SubscriptionManager.DEFAULT_SUB_ID
+    public static final int DEFAULT_SUBSCRIPTION = 0;
+
+    // FIXME: This looks to be used as invalid phoneId, rename
+    // or use SubscriptionManager.INVALID_SUB_ID
+    public static final int INVALID_SUBSCRIPTION = -1;
+
+    public static final int RIL_CARD_MAX_APPS    = 8;
+
+    public static final int DEFAULT_CARD_INDEX   = 0;
+
+    public static final int MAX_PHONE_COUNT_SINGLE_SIM = 1;
+
+    public static final int MAX_PHONE_COUNT_DUAL_SIM = 2;
+
+    public static final int MAX_PHONE_COUNT_TRI_SIM = 3;
+
+    public static final String SUBSCRIPTION_KEY  = "subscription";
+
+    public static final String SLOT_KEY  = "slot";
+
+    public static final String SUB_SETTING  = "subSettings";
+
+    public static final int SUB1 = 0;
+    public static final int SUB2 = 1;
+    public static final int SUB3 = 2;
+
+    public static final int EVENT_SUBSCRIPTION_ACTIVATED   = 500;
+    public static final int EVENT_SUBSCRIPTION_DEACTIVATED = 501;
+
+    // TODO: Remove these constants and use an int instead.
+    public static final int SIM_ID_1 = 0;
+    public static final int SIM_ID_2 = 1;
+    public static final int SIM_ID_3 = 2;
+    public static final int SIM_ID_4 = 3;
+
+    // ICC SIM Application Types
+    // TODO: Replace the IccCardApplicationStatus.AppType enums with these constants
+    public static final int APPTYPE_UNKNOWN = 0;
+    public static final int APPTYPE_SIM = 1;
+    public static final int APPTYPE_USIM = 2;
+    public static final int APPTYPE_RUIM = 3;
+    public static final int APPTYPE_CSIM = 4;
+    public static final int APPTYPE_ISIM = 5;
+
+    public enum CardUnavailableReason {
+        REASON_CARD_REMOVED,
+        REASON_RADIO_UNAVAILABLE,
+        REASON_SIM_REFRESH_RESET
+    };
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 815211c..0271d0a 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -57,6 +57,7 @@
                                                  retries needed */
     int MISSING_RESOURCE = 16;                /* no logical channel available */
     int NO_SUCH_ELEMENT = 17;                 /* application not found on SIM */
+    int SUBSCRIPTION_NOT_SUPPORTED = 26;      /* Subscription not supported */
 
     /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */
     int NETWORK_MODE_WCDMA_PREF     = 0; /* GSM/WCDMA (WCDMA preferred) */
@@ -85,6 +86,7 @@
     int CDMA_PHONE = 2;
     int SIP_PHONE  = 3;
     int THIRD_PARTY_PHONE = 4;
+    int IMS_PHONE = 5;
 
     int LTE_ON_CDMA_UNKNOWN = -1;
     int LTE_ON_CDMA_FALSE = 0;
@@ -282,6 +284,11 @@
     int RIL_REQUEST_NV_WRITE_CDMA_PRL = 120;
     int RIL_REQUEST_NV_RESET_CONFIG = 121;
     int RIL_REQUEST_SET_RADIO_MODE = 122;
+    int RIL_REQUEST_DATA_IDLE = 123;
+    int RIL_REQUEST_SET_UICC_SUBSCRIPTION = 124;
+    int RIL_REQUEST_ALLOW_DATA = 125;
+    int RIL_REQUEST_GET_HARDWARE_CONFIG = 126;
+    int RIL_REQUEST_ICC_SIM_AUTHENTICATION = 127;
 
     int RIL_UNSOL_RESPONSE_BASE = 1000;
     int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
@@ -322,4 +329,7 @@
     int RIL_UNSOL_VOICE_RADIO_TECH_CHANGED = 1035;
     int RIL_UNSOL_CELL_INFO_LIST = 1036;
     int RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED = 1037;
+    int RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED = 1038;
+    int RIL_UNSOL_SRVCC_STATE_NOTIFY = 1039;
+    int RIL_UNSOL_HARDWARE_CONFIG_CHANGED = 1040;
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 9ad2d42..5954947 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.content.Intent;
+
 /**
  * The intents that the telephony services broadcast.
  *
@@ -48,7 +50,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -69,7 +71,7 @@
      *
      * <p class="note">
      * Requires no permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -88,7 +90,7 @@
      *
      * <p class="note">
      * Requires no permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -114,7 +116,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -136,7 +138,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -176,7 +178,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -207,7 +209,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -225,7 +227,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -243,7 +245,7 @@
      *
      * <p class="note">
      * Requires the READ_PHONE_STATE permission.
-     * 
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
@@ -315,4 +317,85 @@
     public static final String EXTRA_PLMN       = "plmn";
     public static final String EXTRA_SHOW_SPN   = "showSpn";
     public static final String EXTRA_SPN        = "spn";
+
+    /**
+     * <p>Broadcast Action: It indicates one column of a siminfo record has been changed
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li><em>columnName</em> - The siminfo column that is updated.</li>
+     *   <li><em>stringContent</em> - The string value of the updated column.</li>
+     *   <li><em>intContent</em> - The int value of the updated column.</li>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_SIMINFO_CONTENT_CHANGE
+            = "android.intent.action.ACTION_SIMINFO_CONTENT_CHANGE";
+
+    /**
+     * <p>Broadcast Action: It indicates one column of a subinfo record has been changed
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li><em>columnName</em> - The siminfo column that is updated.</li>
+     *   <li><em>stringContent</em> - The string value of the updated column.</li>
+     *   <li><em>intContent</em> - The int value of the updated column.</li>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_SUBINFO_CONTENT_CHANGE
+            = "android.intent.action.ACTION_SUBINFO_CONTENT_CHANGE";
+
+    /**
+     * <p>Broadcast Action: It indicates siminfo update is completed when SIM inserted state change
+     * The intent will have the following extra values:</p>
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_SIMINFO_UPDATED
+            = "android.intent.action.ACTION_SIMINFO_UPDATED";
+
+    /**
+     * <p>Broadcast Action: It indicates subinfo record update is completed
+     * when SIM inserted state change
+     * The intent will have the following extra values:</p>
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_SUBINFO_RECORD_UPDATED
+            = "android.intent.action.ACTION_SUBINFO_RECORD_UPDATED";
+
+    public static final String EXTRA_COLUMN_NAME = "columnName";
+    public static final String EXTRA_INT_CONTENT = "intContent";
+    public static final String EXTRA_STRING_CONTENT = "stringContent";
+
+    /**
+     * Broadcast Action: The default subscription has changed.  This has the following
+     * extra values:</p>
+     * <ul>
+     *   <li><em>subscription</em> - A int, the current default subscription.</li>
+     * </ul>
+     */
+    public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED
+            = "android.intent.action.ACTION_DEFAULT_SUBSCRIPTION_CHANGED";
+
+    /**
+     * Broadcast Action: The default data subscription has changed.  This has the following
+     * extra values:</p>
+     * <ul>
+     *   <li><em>subscription</em> - A int, the current data default subscription.</li>
+     * </ul>
+     */
+    public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED
+            = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+
+    /**
+     * Broadcast Action: The default voice subscription has changed.  This has the following
+     * extra values:</p>
+     * <ul>
+     *   <li><em>subscription</em> - A int, the current voice default subscription.</li>
+     * </ul>
+     */
+    public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED
+            = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index f95e081..5ec4247 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -187,4 +187,26 @@
      * Ignore RIL_UNSOL_NITZ_TIME_RECEIVED completely, used for debugging/testing.
      */
     static final String PROPERTY_IGNORE_NITZ = "telephony.test.ignore.nitz";
+
+     /**
+     * Property to set multi sim feature.
+     * Type:  String(dsds, dsda)
+     */
+    static final String PROPERTY_MULTI_SIM_CONFIG = "persist.radio.multisim.config";
+
+    /**
+     * Property to store default subscription.
+     */
+    static final String PROPERTY_DEFAULT_SUBSCRIPTION = "persist.radio.default.sub";
+
+    /**
+     * Property to enable MMS Mode.
+     * Type: string ( default = silent, enable to = prompt )
+     */
+    static final String PROPERTY_MMS_TRANSACTION = "mms.transaction";
+
+    /**
+     * Set to the sim count.
+     */
+    static final String PROPERTY_SIM_COUNT = "ro.telephony.sim.count";
 }
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 0d9e4f1..b12795c 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := core core-junit framework
+LOCAL_JAVA_LIBRARIES := core-libart core-junit framework
 LOCAL_STATIC_JAVA_LIBRARIES := junit-runner
 
 LOCAL_MODULE:= android.test.runner
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..59036da 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -44,6 +44,7 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -304,6 +305,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
         throw new UnsupportedOperationException();
@@ -707,8 +714,8 @@
      * @hide
      */
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId,
+            int flags) {
         throw new UnsupportedOperationException();
     }
 
@@ -716,7 +723,7 @@
      * @hide
      */
     @Override
-    public void clearForwardingIntentFilters(int userIdOrig) {
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
         throw new UnsupportedOperationException();
     }
 
@@ -724,4 +731,20 @@
     public PackageInstaller getPackageInstaller() {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void addCrossProfileIntentsForPackage(String packageName, int sourceUserId,
+            int targetUserId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    public Bitmap getUserIcon(int userId) {
+        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/IdleServiceTest/Android.mk b/tests/IdleServiceTest/Android.mk
deleted file mode 100644
index a7879c5..0000000
--- a/tests/IdleServiceTest/Android.mk
+++ /dev/null
@@ -1,13 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := IdleServiceTest
-LOCAL_CERTIFICATE := platform
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-include $(BUILD_PACKAGE)
diff --git a/tests/IdleServiceTest/AndroidManifest.xml b/tests/IdleServiceTest/AndroidManifest.xml
deleted file mode 100644
index 16d2324..0000000
--- a/tests/IdleServiceTest/AndroidManifest.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?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.android.idleservicetest">
-
-    <application>
-        <service android:name="TestService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_IDLE_SERVICE" >
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
-        </service>
-
-        <service android:name="CrashingTestService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_IDLE_SERVICE" >
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
-        </service>
-
-        <service android:name="TimeoutTestService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_IDLE_SERVICE" >
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
-        </service>
-
-        <!-- UnpermissionedTestService should never run because it does
-             not require the necessary permission in its <service> block -->
-        <service android:name="UnpermissionedTestService"
-                 android:exported="true"
-                 android:enabled="true" >
-            <intent-filter>
-                <action android:name="android.service.idle.IdleService" />
-            </intent-filter>
-        </service>
-
-    </application>
-</manifest>
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java
deleted file mode 100644
index 022ebcf..0000000
--- a/tests/IdleServiceTest/src/com/android/idleservicetest/CrashingTestService.java
+++ /dev/null
@@ -1,52 +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.idleservicetest;
-
-import android.app.maintenance.IdleService;
-import android.os.Handler;
-import android.util.Log;
-
-public class CrashingTestService extends IdleService {
-    static final String TAG = "CrashingTestService";
-
-    String mNull = null;
-
-    @Override
-    public boolean onIdleStart() {
-        Log.i(TAG, "Idle maintenance: onIdleStart()");
-
-        Handler h = new Handler();
-        Runnable r = new Runnable() {
-            @Override
-            public void run() {
-                Log.i(TAG, "Explicitly crashing");
-                if (mNull.equals("")) {
-                    Log.i(TAG, "won't happen");
-                }
-            }
-        };
-        Log.i(TAG, "Posting explicit crash in 15 seconds");
-        h.postDelayed(r, 15 * 1000);
-        return true;
-    }
-
-    @Override
-    public void onIdleStop() {
-        Log.i(TAG, "Idle maintenance: onIdleStop()");
-    }
-
-}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java
deleted file mode 100644
index 7e9805f..0000000
--- a/tests/IdleServiceTest/src/com/android/idleservicetest/TestService.java
+++ /dev/null
@@ -1,48 +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.idleservicetest;
-
-import android.app.maintenance.IdleService;
-import android.os.Handler;
-import android.util.Log;
-
-public class TestService extends IdleService {
-    static final String TAG = "TestService";
-
-    @Override
-    public boolean onIdleStart() {
-        Log.i(TAG, "Idle maintenance: onIdleStart()");
-
-        Handler h = new Handler();
-        Runnable r = new Runnable() {
-            @Override
-            public void run() {
-                Log.i(TAG, "Explicitly finishing idle");
-                finishIdle();
-            }
-        };
-        Log.i(TAG, "Posting explicit finish in 15 seconds");
-        h.postDelayed(r, 15 * 1000);
-        return true;
-    }
-
-    @Override
-    public void onIdleStop() {
-        Log.i(TAG, "Idle maintenance: onIdleStop()");
-    }
-
-}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java
deleted file mode 100644
index b2ba21b..0000000
--- a/tests/IdleServiceTest/src/com/android/idleservicetest/TimeoutTestService.java
+++ /dev/null
@@ -1,36 +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.idleservicetest;
-
-import android.app.maintenance.IdleService;
-import android.util.Log;
-
-public class TimeoutTestService extends IdleService {
-    private static final String TAG = "TimeoutTestService";
-
-    @Override
-    public boolean onIdleStart() {
-        Log.i(TAG, "onIdleStart() but anticipating time-slice timeout");
-        return true;
-    }
-
-    @Override
-    public void onIdleStop() {
-        Log.i(TAG, "onIdleStop() so we're done");
-    }
-
-}
diff --git a/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java b/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java
deleted file mode 100644
index b9fe32b..0000000
--- a/tests/IdleServiceTest/src/com/android/idleservicetest/UnpermissionedTestService.java
+++ /dev/null
@@ -1,38 +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.idleservicetest;
-
-import android.app.maintenance.IdleService;
-import android.util.Log;
-
-// Should never be invoked because its manifest declaration does not
-// require the necessary permission.
-public class UnpermissionedTestService extends IdleService {
-    private static final String TAG = "UnpermissionedTestService";
-
-    @Override
-    public boolean onIdleStart() {
-        Log.e(TAG, "onIdleStart() for this service should never be called!");
-        return false;
-    }
-
-    @Override
-    public void onIdleStop() {
-        Log.e(TAG, "onIdleStop() for this service should never be called!");
-    }
-
-}
diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
index 7f4961b..d3429ff 100644
--- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml
+++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical" >
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="match_parent"
         android:layout_weight="1"
         android:orientation="vertical">
 
@@ -54,6 +54,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/constraints"
+            android:layout_margin="15dp"
             android:textSize="18dp"/>
         <LinearLayout
             android:layout_width="match_parent"
@@ -83,43 +84,81 @@
                 </RadioGroup>
 
                 </LinearLayout>
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/timing"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginLeft="15dp"
-                    android:textSize="17dp"
-                    android:text="@string/delay"/>
-                <EditText
-                    android:id="@+id/delay_time"
-                    android:layout_width="60dp"
-                    android:layout_height="wrap_content"
-                    android:inputType="number"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/deadline"
-                    android:textSize="17dp"/>
-                <EditText
-                    android:id="@+id/deadline_time"
-                    android:layout_width="60dp"
-                    android:layout_height="wrap_content"
-                    android:inputType="number"/>
-            </LinearLayout>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/timing"/>
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="15dp"
+                        android:textSize="17dp"
+                        android:text="@string/delay"/>
+                    <EditText
+                        android:id="@+id/delay_time"
+                        android:layout_width="60dp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/deadline"
+                        android:textSize="17dp"/>
+                    <EditText
+                        android:id="@+id/deadline_time"
+                        android:layout_width="60dp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/charging_caption"
+                        android:layout_marginRight="15dp"/>
+                    <CheckBox
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:id="@+id/checkbox_charging"
+                        android:text="@string/charging_text"/>
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/idle_caption"
+                        android:layout_marginRight="15dp"/>
+                    <CheckBox
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:id="@+id/checkbox_idle"
+                        android:text="@string/idle_mode_text"/>
+                </LinearLayout>
 
             </LinearLayout>
         <Button
             android:id="@+id/schedule_button"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_margin="40dp"
+            android:layout_marginTop="20dp"
+            android:layout_marginLeft="40dp"
+            android:layout_marginRight="40dp"
             android:onClick="scheduleJob"
             android:text="@string/schedule_job_button_text"/>
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="40dp"
+            android:layout_marginRight="40dp"
+            android:onClick="cancelAllJobs"
+            android:text="@string/cancel_all_jobs_button_text"/>
     </LinearLayout>
-</LinearLayout>
+</ScrollView>
diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml
index 824d4b1..eebfb19 100644
--- a/tests/JobSchedulerTestApp/res/values/strings.xml
+++ b/tests/JobSchedulerTestApp/res/values/strings.xml
@@ -20,9 +20,13 @@
     <string name="onstarttask">onStartTask</string>
     <string name="defaultparamtext">task params will show up here.</string>
     <string name="schedule_job_button_text">Schedule Job</string>
+    <string name="cancel_all_jobs_button_text">Cancel all</string>
     <string name="app_name">Job Scheduler Test</string>
     <string name="finish_job_button_text">taskFinished</string>
-    <string name="manual_sync_text">Manual Sync</string>
+    <string name="idle_mode_text">Requires device in idle mode.</string>
+    <string name="charging_caption">Charging:</string>
+    <string name="charging_text">Requires device plugged in.</string>
+    <string name="idle_caption">Idle:</string>
     <string name="constraints">Constraints</string>
     <string name="connectivity">Connectivity:</string>
     <string name="any">Any</string>
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
index 15050ef..e15929d 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
@@ -19,7 +19,9 @@
 import android.app.Activity;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Bundle;
@@ -28,6 +30,7 @@
 import android.os.Messenger;
 import android.text.TextUtils;
 import android.view.View;
+import android.widget.CheckBox;
 import android.widget.EditText;
 import android.widget.RadioButton;
 import android.widget.TextView;
@@ -60,7 +63,8 @@
         mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
         mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
         mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
-
+        mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
+        mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
         mServiceComponent = new ComponentName(this, TestJobService.class);
         // Start service and provide it a way to communicate with us.
         Intent startServiceIntent = new Intent(this, TestJobService.class);
@@ -79,6 +83,9 @@
     EditText mDeadlineEditText;
     RadioButton mWiFiConnectivityRadioButton;
     RadioButton mAnyConnectivityRadioButton;
+    CheckBox mRequiresChargingCheckBox;
+    CheckBox mRequiresIdleCheckbox;
+
     ComponentName mServiceComponent;
     /** Service object to interact scheduled jobs. */
     TestJobService mTestService;
@@ -124,24 +131,32 @@
 
         String delay = mDelayEditText.getText().toString();
         if (delay != null && !TextUtils.isEmpty(delay)) {
-            builder.setMinimumLatency(Long.valueOf(delay));
+            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
         }
         String deadline = mDeadlineEditText.getText().toString();
         if (deadline != null && !TextUtils.isEmpty(deadline)) {
-            builder.setOverrideDeadline(Long.valueOf(deadline));
+            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
         }
-        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isSelected();
-        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isSelected();
+        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
+        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
         if (requiresUnmetered) {
             builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED);
         } else if (requiresAnyConnectivity) {
             builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY);
         }
+        builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
+        builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
 
         mTestService.scheduleJob(builder.build());
 
     }
 
+    public void cancelAllJobs(View v) {
+        JobScheduler tm =
+                (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        tm.cancelAll();
+    }
+
     /**
      * UI onclick listener to call jobFinished() in our service.
      */
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/TtsEnginesTests.java b/tests/TtsTests/src/com/android/speech/tts/TtsEnginesTests.java
new file mode 100644
index 0000000..45e5216
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/TtsEnginesTests.java
@@ -0,0 +1,64 @@
+package com.android.speech.tts;
+
+import android.speech.tts.TtsEngines;
+import android.test.InstrumentationTestCase;
+
+import java.util.Locale;
+
+public class TtsEnginesTests extends InstrumentationTestCase {
+    private TtsEngines mTtsHelper;
+
+    @Override
+    public void setUp() {
+        mTtsHelper = new TtsEngines(getInstrumentation().getContext());
+    }
+
+    public void testParseLocaleString() {
+        assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng-usa"));
+        assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng-USA"));
+        assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("en-US"));
+        assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("en_us"));
+        assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng_US"));
+        assertEquals(new Locale("en", "US", "foobar"),
+                mTtsHelper.parseLocaleString("eng_US-foobar"));
+        assertEquals(new Locale("en", "", "foobar"), mTtsHelper.parseLocaleString("eng__foobar"));
+        assertNull(mTtsHelper.parseLocaleString("cc_xx_barbar"));
+        assertNull(mTtsHelper.parseLocaleString("cc--barbar"));
+
+        assertEquals(new Locale("en"), mTtsHelper.parseLocaleString("eng"));
+        assertEquals(new Locale("en","US","var"), mTtsHelper.parseLocaleString("eng-USA-var"));
+    }
+
+    public void testToOldLocaleStringFormat() {
+        assertArraysEqual(new String[]{"deu", "DEU", ""},
+                TtsEngines.toOldLocaleStringFormat(new Locale("de", "DE")));
+        assertArraysEqual(new String[]{"deu", "", ""},
+                TtsEngines.toOldLocaleStringFormat(new Locale("de")));
+        assertArraysEqual(new String[]{"eng", "", ""},
+                TtsEngines.toOldLocaleStringFormat(new Locale("en")));
+        assertArraysEqual(new String[]{"eng", "USA", ""},
+                TtsEngines.toOldLocaleStringFormat(new Locale("foo")));
+    }
+
+    public void testGetLocalePrefForEngine() {
+        assertEquals(new Locale("en", "US"),
+                mTtsHelper.getLocalePrefForEngine("foo","foo:en-US"));
+        assertEquals(new Locale("en", "US"),
+                mTtsHelper.getLocalePrefForEngine("foo","foo:eng-usa"));
+        assertEquals(new Locale("en", "US"),
+                mTtsHelper.getLocalePrefForEngine("foo","foo:eng_USA"));
+        assertEquals(new Locale("de", "DE"),
+                mTtsHelper.getLocalePrefForEngine("foo","foo:deu-deu"));
+        assertEquals(Locale.getDefault(),
+                mTtsHelper.getLocalePrefForEngine("foo","foo:,bar:xx"));
+        assertEquals(Locale.getDefault(),
+                mTtsHelper.getLocalePrefForEngine("other","foo:,bar:xx"));
+    }
+
+    private void assertArraysEqual(String[] expected, String[] actual) {
+        assertEquals("array length", expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("index " + i, expected[i], actual[i]);
+        }
+    }
+}
\ No newline at end of file
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..890214f 100644
--- a/tests/VectorDrawableTest/AndroidManifest.xml
+++ b/tests/VectorDrawableTest/AndroidManifest.xml
@@ -17,12 +17,41 @@
 
 <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="BitmapDrawableDupe"
+            android:label="Bitmap Performance of clones" >
+            <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 +62,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 +79,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 +87,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
         <activity
             android:name="VectorDrawableStaticPerf"
@@ -71,9 +96,7 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
-
         <activity
             android:name="VectorCheckbox"
             android:label="On a Checkbox" >
@@ -82,7 +105,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
         <activity
             android:name="VectorPathChecking"
@@ -92,7 +114,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
     </application>
 
diff --git a/tests/VectorDrawableTest/res/anim/animation_favorite.xml b/tests/VectorDrawableTest/res/anim/animation_favorite.xml
new file mode 100644
index 0000000..c81ba40
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_favorite.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <objectAnimator
+        android:duration="8000"
+        android:interpolator="@interpolator/custom_path_interpolator_favorite"
+        android:propertyName="pathData"
+        android:repeatCount="-1"
+        android:valueFrom="@string/round_box"
+        android:valueTo="@string/heart"
+        android:valueType="pathType" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/animation_favorite02.xml b/tests/VectorDrawableTest/res/anim/animation_favorite02.xml
new file mode 100644
index 0000000..15d3b8e
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_favorite02.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" >
+
+    <objectAnimator
+        android:duration="8000"
+        android:interpolator="@interpolator/custom_path_interpolator_favorite"
+        android:propertyName="scaleX"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="20" />
+    <objectAnimator
+        android:duration="8000"
+        android:interpolator="@interpolator/custom_path_interpolator_favorite"
+        android:propertyName="scaleY"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="20" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/animation_grouping_1_01.xml b/tests/VectorDrawableTest/res/anim/animation_grouping_1_01.xml
new file mode 100644
index 0000000..8d82d05
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_grouping_1_01.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="3300"
+        android:propertyName="rotation"
+        android:valueFrom="0"
+        android:valueTo="360"
+        android:repeatCount="-1" />
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.xml b/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.xml
new file mode 100644
index 0000000..ae63203
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.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" >
+
+    <objectAnimator
+        android:duration="2000"
+        android:interpolator="@interpolator/custom_path_interpolator_favorite"
+        android:propertyName="scaleX"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="20" />
+    <objectAnimator
+        android:duration="2000"
+        android:interpolator="@interpolator/custom_path_interpolator_favorite"
+        android:propertyName="scaleY"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="20" />
+
+</set>
\ No newline at end of file
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..0c1073e
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
@@ -0,0 +1,28 @@
+<?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"
+        android:interpolator="@interpolator/custom_path_interpolator"
+        />
+
+</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..4d0aae1
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
@@ -0,0 +1,24 @@
+<?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:propertyXName="translateX"
+        android:propertyYName="translateY"
+        android:pathData="m0,0 q 150, 300 150, 0 t 150, 0, t 150, 0 t -150 0 t -150 0 t -150 0 z" />
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
new file mode 100644
index 0000000..92b1ab5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation05.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"
+     android:ordering="sequentially" >
+
+    <objectAnimator
+        android:duration="3000"
+        android:propertyName="pathData"
+        android:valueFrom="M300,70 l 0,-70 70,70 0,0   -70,70z"
+        android:valueTo=  "M300,70 l 0,-70 70,0  0,140 -70,0 z"
+        android:valueType="pathType"/>
+
+    <objectAnimator
+        android:duration="3000"
+        android:propertyName="pathData"
+        android:valueFrom="@string/rectangle2"
+        android:valueTo="@string/equal2"
+        android:valueType="pathType"/>
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml
new file mode 100644
index 0000000..f510256
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml
@@ -0,0 +1,45 @@
+<?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="1300"
+        android:interpolator="@interpolator/trim_start_interpolator"
+        android:propertyName="trimPathStart"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="0.75"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:duration="1300"
+        android:interpolator="@interpolator/trim_end_interpolator"
+        android:propertyName="trimPathEnd"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="0.75"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:duration="1300"
+        android:interpolator="@android:anim/linear_interpolator"
+        android:propertyName="trimPathOffset"
+        android:repeatCount="-1"
+        android:valueFrom="0"
+        android:valueTo="1.25"
+        android:valueType="floatType" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg b/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg
new file mode 100644
index 0000000..dc8c197
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg
Binary files differ
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..19b82ad
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -0,0 +1,41 @@
+<!--
+ 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="v"
+        android:animation="@anim/trim_path_animation05" />
+    <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" />
+    <target
+        android:name="rotationGroup"
+        android:animation="@anim/trim_path_animation04" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml
new file mode 100644
index 0000000..9d8381f
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml
@@ -0,0 +1,26 @@
+<!--
+ 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_drawable_favorite" >
+
+    <target
+        android:name="favorite"
+        android:animation="@anim/animation_favorite" />
+    <target
+        android:name="root"
+        android:animation="@anim/animation_favorite02" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..4a7e4f6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml
@@ -0,0 +1,26 @@
+<!--
+ 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_drawable_grouping_1" >
+
+    <target
+        android:name="sun"
+        android:animation="@anim/animation_grouping_1_01" />
+    <target
+        android:name="earth"
+        android:animation="@anim/animation_grouping_1_01" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml
new file mode 100644
index 0000000..6621e41
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml
@@ -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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/vector_drawable_progress_bar" >
+
+    <target
+        android:name="pie1"
+        android:animation="@anim/trim_path_animation_progress_bar" />
+
+</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..a212def 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
@@ -23,20 +23,77 @@
         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="#FF777777"
+            android:strokeWidth="70"
+            android:trimPathEnd=".75"
+            android:trimPathOffset="0"
+            android:trimPathStart="0" />
+        <path
+            android:name="v"
+            android:fill="#000000"
+            android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
+
+        <group
+            android:name="translateToCenterGroup"
+            android:rotation="0.0"
+            android:translateX="200.0"
+            android:translateY="200.0" >
+            <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="#FFFF0000"
+                    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_drawable_favorite.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_favorite.xml
new file mode 100644
index 0000000..c8840f5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_favorite.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="128dp"
+        android:width="128dp" />
+
+    <viewport
+        android:viewportHeight="480"
+        android:viewportWidth="480" />
+
+    <group
+        android:name="root"
+        android:translateX="240.0"
+        android:translateY="240.0" >
+        <path
+            android:name="favorite"
+            android:fill="#ff000000"
+            android:pathData="M2.100006104,-6
+                C0.1449127197,-6,1.600006104,-5.975006104,0,-5.975006104
+                C-1.574996948,-5.975006104,0.00309753418,-6,-1.949996948,-6
+                C-4.492996216,-6,-5.949996948,-3.718399048,-5.949996948,-1.149993896
+                C-5.949996948,2.379302979,-5.699996948,5.100006104,0,5.100006104
+                C5.699996948,5.100006104,6,2.379302979,6,-1.149993896
+                C6,-3.718399048,4.643005371,-6,2.100006104,-6" />
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..558de94
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="256"
+        android:viewportWidth="256" />
+
+    <group
+        android:name="shape_layer_1"
+        android:translateX="128"
+        android:translateY="128" >
+        <group android:name="sun" >
+            <path
+                android:name="ellipse_path_1"
+                android:fill="#ffff8000"
+                android:pathData="m -25 0 a 25,25 0 1,0 50,0 a 25,25 0 1,0 -50,0" />
+
+            <group
+                android:name="earth"
+                android:translateX="75" >
+                <path
+                    android:name="ellipse_path_1_1"
+                    android:fill="#ff5656ea"
+                    android:pathData="m -10 0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0" />
+
+                <group
+                    android:name="moon"
+                    android:translateX="25" >
+                    <path
+                        android:name="ellipse_path_1_2"
+                        android:fill="#ffadadad"
+                        android:pathData="m -5 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml
new file mode 100644
index 0000000..956c600
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml
@@ -0,0 +1,52 @@
+<!--
+ 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="64"
+        android:viewportWidth="64" />
+
+    <group
+        android:name="root"
+        android:pivotX="0.0"
+        android:pivotY="0.0"
+        android:rotation="0"
+        android:translateX="32.0"
+        android:translateY="32.0" >
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="0" >
+            <path
+                android:name="pie1"
+                android:fill="#00000000"
+                android:pathData="M0, 0 m 0, -9.5 a 9.5,9.5 0 1,1 0,19 a 9.5,9.5 0 1,1 0,-19"
+                android:stroke="#89cff0"
+                android:strokeLineCap="round"
+                android:strokeLineJoin="miter"
+                android:strokeWidth="2"
+                android:trimPathEnd="0.1"
+                android:trimPathOffset="0"
+                android:trimPathStart="0" />
+        </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/interpolator/custom_path_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
new file mode 100644
index 0000000..0cffa0a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="m0,0q0.4,0.05 0.6,0.3t0.3,0.3l0.1,0.4" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml
new file mode 100644
index 0000000..935d5b5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="L0.1, 0 C0.10066,0 0.198,1 0.2, 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml
new file mode 100644
index 0000000..8c57395
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="L 0.09 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml
new file mode 100644
index 0000000..b2770cd
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="C0.2,0 0.1,1 0.5, 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml
new file mode 100644
index 0000000..798f7e6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="L0.5,0 C 0.7,0 0.6,1 1, 1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 64163c2..54cffa8 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
@@ -15,4 +16,13 @@
 -->
 
 <resources>
-</resources>
+
+    <string name="twoLinePathData">"M 0,0 v 100 M 0,0 h 100"</string>
+    <string name="triangle"> "M300,70 l 0,-70 70,70 0,0   -70,70z"</string>
+    <string name="rectangle">"M300,70 l 0,-70 70,0  0,140 -70,0 z"</string>
+    <string name="rectangle2">"M300,70 l 0,-70 70,0  0,70z M300,70  l 70,0 0,70 -70,0z"</string>
+    <string name="equal2">    "M300,35 l 0,-35 70,0  0,35z M300,105 l 70,0 0,35 -70,0z"</string>
+    <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
+    <string name="heart">    "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
+
+</resources>
\ No newline at end of file
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..b1ba0dd
--- /dev/null
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+import android.widget.GridLayout;
+import android.widget.ScrollView;
+
+public class AnimatedVectorDrawableTest extends Activity implements View.OnClickListener{
+    private static final String LOGCAT = "AnimatedVectorDrawableTest";
+
+    protected int[] icon = {
+            R.drawable.animation_vector_drawable_grouping_1,
+            R.drawable.animation_vector_progress_bar,
+            R.drawable.animation_vector_drawable_favorite,
+            R.drawable.animation_vector_drawable01,
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ScrollView scrollView = new ScrollView(this);
+        GridLayout container = new GridLayout(this);
+        scrollView.addView(container);
+        container.setColumnCount(1);
+
+        for (int i = 0; i < icon.length; i++) {
+            Button button = new Button(this);
+            button.setWidth(400);
+            button.setHeight(400);
+            button.setBackgroundResource(icon[i]);
+            container.addView(button);
+            button.setOnClickListener(this);
+        }
+
+        setContentView(scrollView);
+    }
+
+    @Override
+    public void onClick(View v) {
+        AnimatedVectorDrawable d = (AnimatedVectorDrawable) v.getBackground();
+        d.start();
+    }
+}
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java
new file mode 100644
index 0000000..36c8f2b
--- /dev/null
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java
@@ -0,0 +1,78 @@
+/*
+ * 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.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.ScrollView;
+
+import java.text.DecimalFormat;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BitmapDrawableDupe extends Activity {
+    private static final String LOGCAT = "VectorDrawable1";
+    protected int[] icon = {
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+            R.drawable.bitmap_drawable01,
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ScrollView scrollView = new ScrollView(this);
+        GridLayout container = new GridLayout(this);
+        scrollView.addView(container);
+        container.setColumnCount(5);
+        container.setBackgroundColor(0xFF888888);
+
+        DecimalFormat df = new DecimalFormat("#.##");
+        long time =  android.os.SystemClock.elapsedRealtimeNanos();
+        for (int i = 0; i < icon.length; i++) {
+            Button button = new Button(this);
+            button.setWidth(200);
+            button.setBackgroundResource(icon[i]);
+            container.addView(button);
+        }
+
+        setContentView(scrollView);
+        time =  android.os.SystemClock.elapsedRealtimeNanos()-time;
+        TextView t = new TextView(this);
+        t.setText("avgS=" + df.format(time / (icon.length * 1000000.)) + " ms");
+        container.addView(t);
+    }
+}
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/Command.cpp b/tools/aapt/Command.cpp
index cc0da15..5fefab6 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -407,6 +407,8 @@
     ICON_ATTR = 0x01010002,
     NAME_ATTR = 0x01010003,
     PERMISSION_ATTR = 0x01010006,
+    EXPORTED_ATTR = 0x01010010,
+    GRANT_URI_PERMISSIONS_ATTR = 0x0101001b,
     RESOURCE_ATTR = 0x01010025,
     DEBUGGABLE_ATTR = 0x0101000f,
     VALUE_ATTR = 0x01010024,
@@ -582,6 +584,10 @@
     return categories;
 }
 
+static void printComponentPresence(const char* componentName) {
+    printf("provides-component:'%s'\n", componentName);
+}
+
 /*
  * Handle the "dump" command, to extract select data from an archive.
  */
@@ -793,19 +799,28 @@
             bool withinSupportsInput = false;
             bool withinReceiver = false;
             bool withinService = false;
+            bool withinProvider = false;
             bool withinIntentFilter = false;
             bool hasMainActivity = false;
             bool hasOtherActivities = false;
             bool hasOtherReceivers = false;
             bool hasOtherServices = false;
+            bool hasIntentFilter = false;
+
             bool hasWallpaperService = false;
             bool hasImeService = false;
             bool hasAccessibilityService = false;
             bool hasPrintService = false;
             bool hasWidgetReceivers = false;
             bool hasDeviceAdminReceiver = false;
-            bool hasIntentFilter = false;
             bool hasPaymentService = false;
+            bool hasDocumentsProvider = false;
+            bool hasCameraActivity = false;
+            bool hasCameraSecureActivity = false;
+            bool hasLauncher = false;
+            bool hasNotificationListenerService = false;
+            bool hasDreamService = false;
+
             bool actMainActivity = false;
             bool actWidgetReceivers = false;
             bool actDeviceAdminEnabled = false;
@@ -815,6 +830,12 @@
             bool actPrintService = false;
             bool actHostApduService = false;
             bool actOffHostApduService = false;
+            bool actDocumentsProvider = false;
+            bool actNotificationListenerService = false;
+            bool actDreamService = false;
+            bool actCamera = false;
+            bool actCameraSecure = false;
+            bool catLauncher = false;
             bool hasMetaHostPaymentCategory = false;
             bool hasMetaOffHostPaymentCategory = false;
 
@@ -825,6 +846,9 @@
             bool hasBindAccessibilityServicePermission = false;
             bool hasBindPrintServicePermission = false;
             bool hasBindNfcServicePermission = false;
+            bool hasRequiredSafAttributes = false;
+            bool hasBindNotificationListenerServicePermission = false;
+            bool hasBindDreamServicePermission = false;
 
             // These two implement the implicit permissions that are granted
             // to pre-1.6 applications.
@@ -962,13 +986,17 @@
                         withinActivity = false;
                         withinService = false;
                         withinReceiver = false;
+                        withinProvider = false;
                         hasIntentFilter = false;
                         isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
                     } else if (depth < 4) {
                         if (withinIntentFilter) {
                             if (withinActivity) {
                                 hasMainActivity |= actMainActivity;
-                                hasOtherActivities |= !actMainActivity;
+                                hasLauncher |= catLauncher;
+                                hasCameraActivity |= actCamera;
+                                hasCameraSecureActivity |= actCameraSecure;
+                                hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
                             } else if (withinReceiver) {
                                 hasWidgetReceivers |= actWidgetReceivers;
                                 hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
@@ -980,9 +1008,15 @@
                                 hasAccessibilityService |= (actAccessibilityService &&
                                         hasBindAccessibilityServicePermission);
                                 hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+                                hasNotificationListenerService |= actNotificationListenerService &&
+                                        hasBindNotificationListenerServicePermission;
+                                hasDreamService |= actDreamService && hasBindDreamServicePermission;
                                 hasOtherServices |= (!actImeService && !actWallpaperService &&
                                         !actAccessibilityService && !actPrintService &&
-                                        !actHostApduService && !actOffHostApduService);
+                                        !actHostApduService && !actOffHostApduService &&
+                                        !actNotificationListenerService);
+                            } else if (withinProvider) {
+                                hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
                             }
                         }
                         withinIntentFilter = false;
@@ -1348,6 +1382,7 @@
                     withinActivity = false;
                     withinReceiver = false;
                     withinService = false;
+                    withinProvider = false;
                     hasIntentFilter = false;
                     hasMetaHostPaymentCategory = false;
                     hasMetaOffHostPaymentCategory = false;
@@ -1356,6 +1391,9 @@
                     hasBindAccessibilityServicePermission = false;
                     hasBindPrintServicePermission = false;
                     hasBindNfcServicePermission = false;
+                    hasRequiredSafAttributes = false;
+                    hasBindNotificationListenerServicePermission = false;
+                    hasBindDreamServicePermission = false;
                     if (withinApplication) {
                         if(tag == "activity") {
                             withinActivity = true;
@@ -1451,11 +1489,43 @@
                                     hasBindPrintServicePermission = true;
                                 } else if (permission == "android.permission.BIND_NFC_SERVICE") {
                                     hasBindNfcServicePermission = true;
+                                } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
+                                    hasBindNotificationListenerServicePermission = true;
+                                } else if (permission == "android.permission.BIND_DREAM_SERVICE") {
+                                    hasBindDreamServicePermission = true;
                                 }
                             } else {
                                 fprintf(stderr, "ERROR getting 'android:permission' attribute for"
                                         " service '%s': %s\n", serviceName.string(), error.string());
                             }
+                        } else if (tag == "provider") {
+                            withinProvider = true;
+
+                            bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
+                                        " %s\n", error.string());
+                                goto bail;
+                            }
+
+                            bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
+                                    GRANT_URI_PERMISSIONS_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
+                                        " %s\n", error.string());
+                                goto bail;
+                            }
+
+                            String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
+                                        " %s\n", error.string());
+                                goto bail;
+                            }
+
+                            hasRequiredSafAttributes |= exported && grantUriPermissions &&
+                                permission == "android.permission.MANAGE_DOCUMENTS";
+
                         } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
                             String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
                             if (error != "") {
@@ -1504,6 +1574,12 @@
                         actDeviceAdminEnabled = false;
                         actHostApduService = false;
                         actOffHostApduService = false;
+                        actDocumentsProvider = false;
+                        actNotificationListenerService = false;
+                        actDreamService = false;
+                        actCamera = false;
+                        actCameraSecure = false;
+                        catLauncher = false;
                     } else if (withinService && tag == "meta-data") {
                         String8 name = getAttribute(tree, NAME_ATTR, &error);
                         if (error != "") {
@@ -1559,6 +1635,11 @@
                             if (action == "android.intent.action.MAIN") {
                                 isMainActivity = true;
                                 actMainActivity = true;
+                            } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
+                                    action == "android.media.action.VIDEO_CAMERA") {
+                                actCamera = true;
+                            } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
+                                actCameraSecure = true;
                             }
                         } else if (withinReceiver) {
                             if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
@@ -1579,6 +1660,14 @@
                                 actHostApduService = true;
                             } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
                                 actOffHostApduService = true;
+                            } else if (action == "android.service.notification.NotificationListenerService") {
+                                actNotificationListenerService = true;
+                            } else if (action == "android.service.dreams.DreamService") {
+                                actDreamService = true;
+                            }
+                        } else if (withinProvider) {
+                            if (action == "android.content.action.DOCUMENTS_PROVIDER") {
+                                actDocumentsProvider = true;
                             }
                         }
                         if (action == "android.intent.action.SEARCH") {
@@ -1598,6 +1687,8 @@
                                 isLauncherActivity = true;
                             } else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
                                 isLeanbackLauncherActivity = true;
+                            } else if (category == "android.intent.category.HOME") {
+                                catLauncher = true;
                             }
                         }
                     }
@@ -1775,37 +1866,56 @@
                 }
             }
 
-            if (hasMainActivity) {
-                printf("main\n");
-            }
             if (hasWidgetReceivers) {
-                printf("app-widget\n");
+                printComponentPresence("app-widget");
             }
             if (hasDeviceAdminReceiver) {
-                printf("device-admin\n");
+                printComponentPresence("device-admin");
             }
             if (hasImeService) {
-                printf("ime\n");
+                printComponentPresence("ime");
             }
             if (hasWallpaperService) {
-                printf("wallpaper\n");
+                printComponentPresence("wallpaper");
             }
             if (hasAccessibilityService) {
-                printf("accessibility\n");
+                printComponentPresence("accessibility");
             }
             if (hasPrintService) {
-                printf("print\n");
+                printComponentPresence("print-service");
             }
             if (hasPaymentService) {
-                printf("payment\n");
+                printComponentPresence("payment");
+            }
+            if (isSearchable) {
+                printComponentPresence("search");
+            }
+            if (hasDocumentsProvider) {
+                printComponentPresence("document-provider");
+            }
+            if (hasLauncher) {
+                printComponentPresence("launcher");
+            }
+            if (hasNotificationListenerService) {
+                printComponentPresence("notification-listener");
+            }
+            if (hasDreamService) {
+                printComponentPresence("dream");
+            }
+            if (hasCameraActivity) {
+                printComponentPresence("camera");
+            }
+            if (hasCameraSecureActivity) {
+                printComponentPresence("camera-secure");
+            }
+
+            if (hasMainActivity) {
+                printf("main\n");
             }
             if (hasOtherActivities) {
                 printf("other-activities\n");
             }
-            if (isSearchable) {
-                printf("search\n");
-            }
-            if (hasOtherReceivers) {
+             if (hasOtherReceivers) {
                 printf("other-receivers\n");
             }
             if (hasOtherServices) {
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/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index f10904c..3d93bbe 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -589,11 +589,11 @@
                 if (bundle->getVerbose()) {
                     printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
                 }
-                size_t baseIndex = UNKNOWN_ERROR;
+                ssize_t baseIndex = -1;
                 if (baseSet->get() != NULL) {
                     baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
                 }
-                if (baseIndex < UNKNOWN_ERROR) {
+                if (baseIndex >= 0) {
                     // look for same flavor.  For a given file (strings.xml, for example)
                     // there may be a locale specific or other flavors - we want to match
                     // the same flavor.
@@ -619,10 +619,10 @@
                     for (size_t overlayGroupIndex = 0;
                             overlayGroupIndex<overlayGroupSize;
                             overlayGroupIndex++) {
-                        size_t baseFileIndex =
+                        ssize_t baseFileIndex =
                                 baseGroup->getFiles().indexOfKey(overlayFiles.
                                 keyAt(overlayGroupIndex));
-                        if (baseFileIndex < UNKNOWN_ERROR) {
+                        if (baseFileIndex >= 0) {
                             if (bundle->getVerbose()) {
                                 printf("found a match (" ZD ") for overlay file %s, for flavor %s\n",
                                         (ZD_TYPE) baseFileIndex,
@@ -1363,7 +1363,11 @@
 
             if (split->isBase()) {
                 resFile = flattenedTable;
-                finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
+                err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
+                if (err != NO_ERROR) {
+                    fprintf(stderr, "Generated resource table is corrupt.\n");
+                    return err;
+                }
             } else {
                 sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"),
                         AaptGroupEntry(), String8());
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index efbba40..1a9f1b9 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2292,8 +2292,14 @@
     if (resId != 0 || !createIfNotFound) {
         return resId;
     }
-    String16 value("false");
 
+    if (mAssetsPackage != package) {
+        mCurrentXmlPos.warning("creating resource for external package %s: %s/%s.",
+                String8(package).string(), String8(type).string(), String8(name).string());
+        mCurrentXmlPos.printf("This will be an error in a future version of AAPT.");
+    }
+
+    String16 value("false");
     status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
     if (status == NO_ERROR) {
         resId = getResId(package, type, name);
@@ -3062,8 +3068,9 @@
             for (size_t i = 0; i < N; ++i) {
                 if (!validResources[i]) {
                     sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
-                    fprintf(stderr, "%s: no entries written for %s/%s\n", log_prefix,
-                            String8(typeName).string(), String8(c->getName()).string());
+                    fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
+                            String8(typeName).string(), String8(c->getName()).string(),
+                            Res_MAKEID(p->getAssignedId() - 1, ti, i));
                     missing_entry = true;
                 }
             }
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 08486e6..1942831 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -30,8 +30,8 @@
 built_framework_dep := $(call java-lib-deps,framework-base)
 built_framework_classes := $(call java-lib-files,framework-base)
 
-built_core_dep := $(call java-lib-deps,core)
-built_core_classes := $(call java-lib-files,core)
+built_core_dep := $(call java-lib-deps,core-libart)
+built_core_classes := $(call java-lib-files,core-libart)
 
 built_ext_dep := $(call java-lib-deps,ext)
 built_ext_classes := $(call java-lib-files,ext)
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 80f3b18..2ee06fc 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/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 09e6878..e1064b1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -197,7 +197,7 @@
     }
 
     @Override
-    public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) {
+    public void onRectangleOnScreenRequested(IBinder window, Rect rectangle) {
         // pass for now.
     }
 
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 8a58d35..07f72c4 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/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 00e1cd8..e83eed7 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -132,5 +132,13 @@
     void enableVerboseLogging(int verbose);
 
     int getVerboseLoggingLevel();
+
+    int getAggressiveHandover();
+
+    void enableAggressiveHandover(int enabled);
+
+    int getAllowScansWithTraffic();
+
+    void setAllowScansWithTraffic(int enabled);
 }
 
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..59b48e4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -342,14 +342,92 @@
 
     /**
      * @hide
+     * Uid of app owning the BSSID
+     */
+    public int bssidOwnerUid;
+
+    /**
+     * @hide
      * BSSID list on which this configuration was seen.
      * TODO: prevent this list to grow infinitely, age-out the results
      */
     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 = -77;
+
+    /** @hide **/
+    public static int BAD_RSSI_24 = -87;
+
+    /** @hide **/
+    public static int GOOD_RSSI_5 = -60;
+
+    /** @hide **/
+    public static int LOW_RSSI_5 = -72;
+
+    /** @hide **/
+    public static int BAD_RSSI_5 = -82;
+
+    /** @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 = -77;
+
+    /** @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
+     * 5GHz band is penalized if the 5GHz RSSI is lower than this threshold **/
+    public static int G_BAND_PREFERENCE_RSSI_THRESHOLD = -75;
+
+    /** @hide
+     * Boost given to RSSI on a home network for the purpose of calculating the score
+     * This adds stickiness to home networks, as defined by:
+     * - less than 4 known BSSIDs
+     * - PSK only
+     * - TODO: add a test to verify that all BSSIDs are behind same gateway
+     ***/
+    public static int HOME_NETWORK_RSSI_BOOST = 5;
+
     /**
      * @hide
      * A summary of the RSSI and Band status for that configuration
@@ -426,11 +504,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 +516,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 +534,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 +543,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 +564,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 +692,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 +733,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 +827,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();
     }
@@ -985,8 +1124,12 @@
             didSelfAdd = source.didSelfAdd;
             lastConnectUid = source.lastConnectUid;
             lastUpdateUid = source.lastUpdateUid;
+            bssidOwnerUid = source.bssidOwnerUid;
             creatorUid = source.creatorUid;
             peerWifiConfiguration = source.peerWifiConfiguration;
+            blackListTimestamp = source.blackListTimestamp;
+            lastConnected = source.lastConnected;
+            lastDisconnected = source.lastDisconnected;
         }
     }
 
@@ -1030,17 +1173,8 @@
         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.writeInt(bssidOwnerUid);
+        dest.writeLong(blackListTimestamp);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1079,26 +1213,8 @@
                 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.bssidOwnerUid = in.readInt();
+                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..e46f916 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,121 @@
     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 linkStuckCount;
+
+    /**
+     * @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,11 +217,35 @@
         mBSSID = null;
         mNetworkId = -1;
         mSupplicantState = SupplicantState.UNINITIALIZED;
-        mRssi = -9999;
+        mRssi = INVALID_RSSI;
         mLinkSpeed = -1;
         mFrequency = -1;
     }
 
+    /** @hide */
+    public void reset() {
+        setInetAddress(null);
+        setBSSID(null);
+        setSSID(null);
+        setNetworkId(-1);
+        setRssi(INVALID_RSSI);
+        setLinkSpeed(-1);
+        setFrequency(-1);
+        setMeteredHint(false);
+        txBad = 0;
+        txSuccess = 0;
+        rxSuccess = 0;
+        txRetries = 0;
+        txBadRate = 0;
+        txSuccessRate = 0;
+        rxSuccessRate = 0;
+        txRetriesRate = 0;
+        lowRssiCount = 0;
+        badRssiCount = 0;
+        linkStuckCount = 0;
+        score = 0;
+    }
+
     /**
      * Copy constructor
      * @hide
@@ -109,6 +262,18 @@
             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;
+            linkStuckCount = source.linkStuckCount;
         }
     }
 
@@ -158,7 +323,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 +331,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 +367,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 +510,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 +541,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 +571,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..a30fb79 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();
@@ -2232,7 +2226,6 @@
         }
     }
 
-
     /**
      * Set wifi verbose log. Called from developer settings.
      * @hide
@@ -2257,4 +2250,53 @@
             return 0;
         }
     }
+
+    /**
+     * Set wifi Aggressive Handover. Called from developer settings.
+     * @hide
+     */
+    public void enableAggressiveHandover(int enabled) {
+        try {
+            mService.enableAggressiveHandover(enabled);
+        } catch (RemoteException e) {
+
+        }
+    }
+
+    /**
+     * Get the WiFi Handover aggressiveness.This is used by settings
+     * to decide what to show within the picker.
+     * @hide
+     */
+    public int getAggressiveHandover() {
+        try {
+            return mService.getAggressiveHandover();
+        } catch (RemoteException e) {
+            return 0;
+        }
+    }
+
+    /**
+     * Set setting for allowing Scans when traffic is ongoing.
+     * @hide
+     */
+    public void setAllowScansWithTraffic(int enabled) {
+        try {
+            mService.setAllowScansWithTraffic(enabled);
+        } catch (RemoteException e) {
+
+        }
+    }
+
+    /**
+     * Get setting for allowing Scans when traffic is ongoing.
+     * @hide
+     */
+    public int getAllowScansWithTraffic() {
+        try {
+            return mService.getAllowScansWithTraffic();
+        } catch (RemoteException e) {
+            return 0;
+        }
+    }
 }
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/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index 1c9c40d..ee2e895 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -26,6 +26,7 @@
 interface IWifiP2pManager
 {
     Messenger getMessenger();
+    Messenger getP2pStateMachineMessenger();
     void setMiracastMode(int mode);
 }
 
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 3ed2406..6409450 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -276,6 +276,13 @@
     public static final String WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION =
         "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED";
 
+    /**
+     * The lookup key for a handover message returned by the WifiP2pService.
+     * @hide
+     */
+    public static final String EXTRA_HANDOVER_MESSAGE =
+            "android.net.wifi.p2p.EXTRA_HANDOVER_MESSAGE";
+
     IWifiP2pManager mService;
 
     private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
@@ -446,6 +453,21 @@
     /** @hide */
     public static final int SET_CHANNEL_SUCCEEDED                   = BASE + 73;
 
+    /** @hide */
+    public static final int GET_HANDOVER_REQUEST                    = BASE + 75;
+    /** @hide */
+    public static final int GET_HANDOVER_SELECT                     = BASE + 76;
+    /** @hide */
+    public static final int RESPONSE_GET_HANDOVER_MESSAGE           = BASE + 77;
+    /** @hide */
+    public static final int INITIATOR_REPORT_NFC_HANDOVER           = BASE + 78;
+    /** @hide */
+    public static final int RESPONDER_REPORT_NFC_HANDOVER           = BASE + 79;
+    /** @hide */
+    public static final int REPORT_NFC_HANDOVER_SUCCEEDED           = BASE + 80;
+    /** @hide */
+    public static final int REPORT_NFC_HANDOVER_FAILED              = BASE + 81;
+
 
     /**
      * Create a new WifiP2pManager instance. Applications use
@@ -627,6 +649,14 @@
     }
 
     /**
+     * Interface for callback invocation when Handover Request or Select Message is available
+     * @hide
+     */
+    public interface HandoverMessageListener {
+        public void onHandoverMessageAvailable(String handoverMessage);
+    }
+
+    /**
      * A channel that connects the application to the Wifi p2p framework.
      * Most p2p operations require a Channel as an argument. An instance of Channel is obtained
      * by doing a call on {@link #initialize}
@@ -687,6 +717,7 @@
                     case START_LISTEN_FAILED:
                     case STOP_LISTEN_FAILED:
                     case SET_CHANNEL_FAILED:
+                    case REPORT_NFC_HANDOVER_FAILED:
                         if (listener != null) {
                             ((ActionListener) listener).onFailure(message.arg1);
                         }
@@ -712,6 +743,7 @@
                     case START_LISTEN_SUCCEEDED:
                     case STOP_LISTEN_SUCCEEDED:
                     case SET_CHANNEL_SUCCEEDED:
+                    case REPORT_NFC_HANDOVER_SUCCEEDED:
                         if (listener != null) {
                             ((ActionListener) listener).onSuccess();
                         }
@@ -745,7 +777,17 @@
                                 onPersistentGroupInfoAvailable(groups);
                         }
                         break;
-                   default:
+                    case RESPONSE_GET_HANDOVER_MESSAGE:
+                        Bundle handoverBundle = (Bundle) message.obj;
+                        if (listener != null) {
+                            String handoverMessage = handoverBundle != null
+                                    ? handoverBundle.getString(EXTRA_HANDOVER_MESSAGE)
+                                    : null;
+                            ((HandoverMessageListener) listener)
+                                    .onHandoverMessageAvailable(handoverMessage);
+                        }
+                        break;
+                    default:
                         Log.d(TAG, "Ignored " + message);
                         break;
                 }
@@ -841,7 +883,20 @@
      * @return Channel instance that is necessary for performing any further p2p operations
      */
     public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
-        Messenger messenger = getMessenger();
+        return initalizeChannel(srcContext, srcLooper, listener, getMessenger());
+    }
+
+    /**
+     * Registers the application with the Wi-Fi framework. Enables system-only functionality.
+     * @hide
+     */
+    public Channel initializeInternal(Context srcContext, Looper srcLooper,
+                                      ChannelListener listener) {
+        return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger());
+    }
+
+    private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
+                                     Messenger messenger) {
         if (messenger == null) return null;
 
         Channel c = new Channel(srcContext, srcLooper, listener);
@@ -1327,4 +1382,62 @@
         }
     }
 
+    /**
+     * Get a reference to P2pStateMachine handler. This is used to establish
+     * a priveleged AsyncChannel communication with WifiP2pService.
+     *
+     * @return Messenger pointing to the WifiP2pService handler
+     * @hide
+     */
+    public Messenger getP2pStateMachineMessenger() {
+        try {
+            return mService.getP2pStateMachineMessenger();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get a handover request message for use in WFA NFC Handover transfer.
+     * @hide
+     */
+    public void getNfcHandoverRequest(Channel c, HandoverMessageListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(GET_HANDOVER_REQUEST, 0, c.putListener(listener));
+    }
+
+
+    /**
+     * Get a handover select message for use in WFA NFC Handover transfer.
+     * @hide
+     */
+    public void getNfcHandoverSelect(Channel c, HandoverMessageListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(GET_HANDOVER_SELECT, 0, c.putListener(listener));
+    }
+
+    /**
+     * @hide
+     */
+    public void initiatorReportNfcHandover(Channel c, String handoverSelect,
+                                              ActionListener listener) {
+        checkChannel(c);
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverSelect);
+        c.mAsyncChannel.sendMessage(INITIATOR_REPORT_NFC_HANDOVER, 0,
+                c.putListener(listener), bundle);
+    }
+
+
+    /**
+     * @hide
+     */
+    public void responderReportNfcHandover(Channel c, String handoverRequest,
+                                              ActionListener listener) {
+        checkChannel(c);
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverRequest);
+        c.mAsyncChannel.sendMessage(RESPONDER_REPORT_NFC_HANDOVER, 0,
+                c.putListener(listener), bundle);
+    }
 }
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..33ccad5 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 */
@@ -541,7 +493,8 @@
                 }
                 if (this.mType.equals("TLS")) {
                     result = this.mRealm.equals(other.mRealm) &&
-                            this.mHomeSpFqdn.equals(other.mHomeSpFqdn);
+                            this.mHomeSpFqdn.equals(other.mHomeSpFqdn) &&
+                            this.mClientCert.equals(other.mClientCert);
                 }
                 if (this.mType.equals("SIM")) {
                     result = this.mMcc.equals(other.mMcc) &&
@@ -560,72 +513,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 +592,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 +615,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 +633,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..33db3f5 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> networkAuthTypeList;
+
+    /** list of roaming consortium OIs */
+    public List<String> roamingConsortiumList;
+
+    /** IP address availability */
+    public IpAddressType ipAddrTypeAvailability;
+
+    /** list of NAI realm */
+    public List<NaiRealm> naiRealmList;
+
+    /** list of 3GPP cellular network */
+    public List<CellularNetwork> cellularNetworkList;
+
+    /** list of fully qualified domain name (FQDN) */
+    public List<String> domainNameList;
+
+    /** HS 2.0 operator friendly name */
+    public String operatorFriendlyName;
+
+    /** HS 2.0 wan metrics */
+    public WanMetrics wanMetrics;
+
+    /** list of HS 2.0 IP proto port */
+    public List<IpProtoPort> connectionCapabilityList;
+
+    /** list of HS 2.0 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 (networkAuthTypeList != null) {
+            sb.append(" networkAuthType: ");
+            for (NetworkAuthType auth : networkAuthTypeList)
+                sb.append("(").append(auth.toString()).append(")");
+        }
+
+        if (roamingConsortiumList != null) {
+            sb.append(" roamingConsortium: ");
+            for (String oi : roamingConsortiumList)
+                sb.append("(").append(oi).append(")");
+        }
+
+        if (ipAddrTypeAvailability != null) {
+            sb.append(" ipAddrTypeAvaibility: ").append("(")
+              .append(ipAddrTypeAvailability.toString()).append(")");
+        }
+
+        if (naiRealmList != null) {
+            sb.append(" naiRealm: ");
+            for (NaiRealm realm : naiRealmList)
+                sb.append("(").append(realm.toString()).append(")");
+        }
+
+        if (cellularNetworkList != null) {
+            sb.append(" cellularNetwork: ");
+            for (CellularNetwork plmn : cellularNetworkList)
+                sb.append("(").append(plmn.toString()).append(")");
+        }
+
+        if (domainNameList != null) {
+            sb.append(" domainName: ");
+            for (String fqdn : domainNameList)
+                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 (connectionCapabilityList != null) {
+            sb.append(" connectionCapability: ");
+            for (IpProtoPort ip : connectionCapabilityList)
+                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 (networkAuthTypeList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(networkAuthTypeList.size());
+            for (NetworkAuthType auth : networkAuthTypeList) {
+                out.writeInt(auth.type);
+                out.writeString(auth.redirectUrl);
+            }
+        }
+
+        if (roamingConsortiumList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(roamingConsortiumList.size());
+            for (String oi : roamingConsortiumList)
+                out.writeString(oi);
+        }
+
+        if (ipAddrTypeAvailability == null) {
+            out.writeInt(IpAddressType.NULL_VALUE);
+        } else {
+            out.writeInt(ipAddrTypeAvailability.availability);
+        }
+
+        if (naiRealmList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(naiRealmList.size());
+            for (NaiRealm realm : naiRealmList) {
+                out.writeInt(realm.encoding);
+                out.writeString(realm.realm);
+            }
+        }
+
+        if (cellularNetworkList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(cellularNetworkList.size());
+            for (CellularNetwork plmn : cellularNetworkList) {
+                out.writeString(plmn.mcc);
+                out.writeString(plmn.mnc);
+            }
+        }
+
+
+        if (domainNameList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(domainNameList.size());
+            for (String fqdn : domainNameList)
+                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 (connectionCapabilityList == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(connectionCapabilityList.size());
+            for (IpProtoPort ip : connectionCapabilityList) {
+                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.networkAuthTypeList = new ArrayList<NetworkAuthType>();
+                        for (int i = 0; i < n; i++) {
+                            NetworkAuthType auth = new NetworkAuthType();
+                            auth.type = in.readInt();
+                            auth.redirectUrl = in.readString();
+                            p.networkAuthTypeList.add(auth);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.roamingConsortiumList = new ArrayList<String>();
+                        for (int i = 0; i < n; i++)
+                            p.roamingConsortiumList.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.naiRealmList = new ArrayList<NaiRealm>();
+                        for (int i = 0; i < n; i++) {
+                            NaiRealm realm = new NaiRealm();
+                            realm.encoding = in.readInt();
+                            realm.realm = in.readString();
+                            p.naiRealmList.add(realm);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.cellularNetworkList = new ArrayList<CellularNetwork>();
+                        for (int i = 0; i < n; i++) {
+                            CellularNetwork plmn = new CellularNetwork();
+                            plmn.mcc = in.readString();
+                            plmn.mnc = in.readString();
+                            p.cellularNetworkList.add(plmn);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.domainNameList = new ArrayList<String>();
+                        for (int i = 0; i < n; i++)
+                            p.domainNameList.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.connectionCapabilityList = 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.connectionCapabilityList.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..e7e6767 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 == null || 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,24 +467,25 @@
         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
      */
-    public List<WifiPasspointCredential> getSavedCredentials() {
+    public List<WifiPasspointCredential> getCredentials() {
         return null;
     }
 
@@ -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) {
+    public void startRemediation(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..f84ac88 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java
@@ -16,10 +16,18 @@
 
 package android.net.wifi.passpoint;
 
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.ScanResult;
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.security.Credentials;
 import android.util.Log;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+
 /** @hide */
 public class WifiPasspointPolicy implements Parcelable {
 
@@ -35,7 +43,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;
@@ -43,12 +51,28 @@
     private int mRestriction;// Permitted values are "HomeSP", "RoamingPartner", or "Unrestricted"
     private boolean mIsHomeSp;
 
+    private final String INT_PRIVATE_KEY = "private_key";
+    private final String INT_PHASE2 = "phase2";
+    private final String INT_PASSWORD = "password";
+    private final String INT_IDENTITY = "identity";
+    private final String INT_EAP = "eap";
+    private final String INT_CLIENT_CERT = "client_cert";
+    private final String INT_CA_CERT = "ca_cert";
+    private final String INT_ANONYMOUS_IDENTITY = "anonymous_identity";
+    private final String INT_SIM_SLOT = "sim_slot";
+    private final String INT_ENTERPRISEFIELD_NAME ="android.net.wifi.WifiConfiguration$EnterpriseField";
+    private final String ISO8601DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+    private final String ENTERPRISE_PHASE2_MSCHAPV2 = "auth=MSCHAPV2";
+    private final String ENTERPRISE_PHASE2_MSCHAP = "auth=MSCHAP";
+
     /** @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;
@@ -87,7 +111,7 @@
     }
 
     /** @hide */
-    public boolean getHomeSp() {
+    public boolean isHomeSp() {
         return mIsHomeSp;
     }
 
@@ -102,8 +126,8 @@
     }
 
     /** @hide */
-    public void setSubscriptionPriority(int priority) {
-        mSubscriptionPriority = priority;
+    public void setCredentialPriority(int priority) {
+        mCredentialPriority = priority;
     }
 
     /** @hide */
@@ -111,14 +135,150 @@
         mRoamingPriority = priority;
     }
 
-    public int getSubscriptionPriority() {
-        return mSubscriptionPriority;
+    public int getCredentialPriority() {
+        return mCredentialPriority;
     }
 
     public int getRoamingPriority() {
         return mRoamingPriority;
     }
 
+    public WifiConfiguration createWifiConfiguration() {
+        WifiConfiguration wfg = new WifiConfiguration();
+        if (mBssid != null) {
+            Log.d(TAG, "create bssid:" + mBssid);
+            wfg.BSSID = mBssid;
+        }
+
+        if (mSsid != null) {
+            Log.d(TAG, "create ssid:" + mSsid);
+            wfg.SSID = mSsid;
+        }
+        //TODO: 1. add pmf configuration
+        //      2. add ocsp configuration
+        //      3. add eap-sim configuration
+        /*Key management*/
+        wfg.status = WifiConfiguration.Status.ENABLED;
+        wfg.allowedKeyManagement.clear();
+        wfg.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        wfg.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+
+        /*Group Ciphers*/
+        wfg.allowedGroupCiphers.clear();
+        wfg.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+        wfg.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+
+        /*Protocols*/
+        wfg.allowedProtocols.clear();
+        wfg.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+        wfg.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
+
+        Class[] enterpriseFieldArray  = WifiConfiguration.class.getClasses();
+        Class<?> enterpriseFieldClass = null;
+
+
+        for(Class<?> myClass : enterpriseFieldArray) {
+            if(myClass.getName().equals(INT_ENTERPRISEFIELD_NAME)) {
+                enterpriseFieldClass = myClass;
+                break;
+            }
+        }
+        Log.d(TAG, "class chosen " + enterpriseFieldClass.getName() );
+
+
+        Field anonymousId = null, caCert = null, clientCert = null,
+              eap = null, identity = null, password = null,
+              phase2 = null, privateKey =  null;
+
+        Field[] fields = WifiConfiguration.class.getFields();
+
+
+        for (Field tempField : fields) {
+            if (tempField.getName().trim().equals(INT_ANONYMOUS_IDENTITY)) {
+                anonymousId = tempField;
+                Log.d(TAG, "field " + anonymousId.getName() );
+            } else if (tempField.getName().trim().equals(INT_CA_CERT)) {
+                caCert = tempField;
+            } else if (tempField.getName().trim().equals(INT_CLIENT_CERT)) {
+                clientCert = tempField;
+                Log.d(TAG, "field " + clientCert.getName() );
+            } else if (tempField.getName().trim().equals(INT_EAP)) {
+                eap = tempField;
+                Log.d(TAG, "field " + eap.getName() );
+            } else if (tempField.getName().trim().equals(INT_IDENTITY)) {
+                identity = tempField;
+                Log.d(TAG, "field " + identity.getName() );
+            } else if (tempField.getName().trim().equals(INT_PASSWORD)) {
+                password = tempField;
+                Log.d(TAG, "field " + password.getName() );
+            } else if (tempField.getName().trim().equals(INT_PHASE2)) {
+                phase2 = tempField;
+                Log.d(TAG, "field " + phase2.getName() );
+
+            } else if (tempField.getName().trim().equals(INT_PRIVATE_KEY)) {
+                privateKey = tempField;
+            }
+        }
+
+
+        Method setValue = null;
+
+        for(Method m: enterpriseFieldClass.getMethods()) {
+            if(m.getName().trim().equals("setValue")) {
+                Log.d(TAG, "method " + m.getName() );
+                setValue = m;
+                break;
+            }
+        }
+
+        try {
+            // EAP
+            String eapmethod = mCredential.getType();
+            Log.d(TAG, "eapmethod:" + eapmethod);
+            setValue.invoke(eap.get(wfg), eapmethod);
+
+            // Username, password, EAP Phase 2
+            if ("TTLS".equals(eapmethod)) {
+                setValue.invoke(phase2.get(wfg), ENTERPRISE_PHASE2_MSCHAPV2);
+                setValue.invoke(identity.get(wfg), mCredential.getUserName());
+                setValue.invoke(password.get(wfg), mCredential.getPassword());
+                setValue.invoke(anonymousId.get(wfg), "anonymous@" + mCredential.getRealm());
+            }
+
+            // EAP CA Certificate
+            String cacertificate = null;
+            String rootCA = mCredential.getCaRootCertPath();
+            if (rootCA == null){
+                cacertificate = null;
+            } else {
+                cacertificate = "keystore://" + Credentials.WIFI + "HS20" + Credentials.CA_CERTIFICATE + rootCA;
+            }
+            Log.d(TAG, "cacertificate:" + cacertificate);
+            setValue.invoke(caCert.get(wfg), cacertificate);
+
+            //User certificate
+            if ("TLS".equals(eapmethod)) {
+                String usercertificate = null;
+                String privatekey = null;
+                String clientCertPath = mCredential.getClientCertPath();
+                if (clientCertPath != null){
+                    privatekey = "keystore://" + Credentials.WIFI + "HS20" + Credentials.USER_PRIVATE_KEY + clientCertPath;
+                    usercertificate = "keystore://" + Credentials.WIFI + "HS20" + Credentials.USER_CERTIFICATE + clientCertPath;
+                }
+                Log.d(TAG, "privatekey:" + privatekey);
+                Log.d(TAG, "usercertificate:" + usercertificate);
+                if (privatekey != null && usercertificate != null) {
+                    setValue.invoke(privateKey.get(wfg), privatekey);
+                    setValue.invoke(clientCert.get(wfg), usercertificate);
+                }
+            }
+        } catch (Exception e) {
+            Log.d(TAG, "createWifiConfiguration err:" + e);
+        }
+
+        return wfg;
+    }
+
     /** {@inheritDoc} @hide */
     public int compareTo(WifiPasspointPolicy another) {
         Log.d(TAG, "this:" + this);
@@ -126,17 +286,17 @@
 
         if (another == null) {
             return -1;
-        } else if (this.mIsHomeSp == true && another.getHomeSp() == false) {
+        } else if (this.mIsHomeSp == true && another.isHomeSp() == false) {
             //home sp priority is higher then roaming
             Log.d(TAG, "compare HomeSP  first, this is HomeSP, another isn't");
             return -1;
-        } else if ((this.mIsHomeSp == true && another.getHomeSp() == true)) {
+        } else if ((this.mIsHomeSp == true && another.isHomeSp() == 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) {
@@ -158,7 +318,7 @@
             } else {
                 return 1;
             }
-        } else if ((this.mIsHomeSp == false && another.getHomeSp() == false)) {
+        } else if ((this.mIsHomeSp == false && another.isHomeSp() == false)) {
             Log.d(TAG, "both RoamingSp");
             //if both roaming sp, compare roaming priority(preferredRoamingPartnerList/<X+>/priority)
             if (this.mRoamingPriority < another.getRoamingPriority()) {
@@ -192,7 +352,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;